并查集维护值(逆序维护)
题目链接:Educational Codeforces Round 119 E
题意:
有
q
q
q次次操作
- 1 x 向数组后面插入 x x x
- 2.x y 将数组中所有 x x x该为 y y y
sol
很容易想到用并查集来维护这些值变成了什么,但是当我们正着去做的时候,后面如果要改变前面已经该过了的值话就会出问题。比如前面你要把1变成2,后面又让1变成3,那么这样操作下来的话,最后1都会变成3 。那要怎么避免呢,我们将操作保存下来;然后反着去维护即可。具体细节看代码
Code
#include <iostream>
using namespace std;
const int N = 5e5 + 10;
int p[N];
int opt[N], x[N], y[N];
void solve()
{
int q;
cin >> q;
rep(i, 1, 5e5) p[i] = i;
for (int i = 1; i <= q; ++ i) {
cin >> opt[i] >> x[i];
if(opt[i] == 2) {
cin >> y[i];
}
}
vector<int> res;
for(int i = q; i ; i -- ) {
if(opt[i] == 1) res.push_back(p[x[i]]);
else {
p[x[i]] = p[y[i]];
}
}
reverse(ALL(res));
for(int x : res) cout << x << " ";
}
signed main(){
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
计数DP
题目链接:Atcoder abc232 E
题意:
现在有一个
N
×
M
N×M
N×M的棋盘,一开始在
x
1
,
y
1
x_1,y_1
x1,y1,需要走到
x
2
,
y
2
x_2,y_2
x2,y2。每次走的方法是,可以移动到同一行或者同一列的任何地方,但不能不动。问方案数,对
998244353
998244353
998244353取模
2
≤
N
,
M
≤
1
0
9
1
≤
K
≤
1
0
6
2 \le N, M \le 10^9 \\ 1\le K \le 10^6
2≤N,M≤1091≤K≤106
Sol:
考虑dp:
对于行
- 状态表示:用 f [ i ] [ 1 ] f[i][1] f[i][1]表示走了 i i i步,走到 x 2 x_2 x2位置的方案数, f [ i ] [ 0 ] f[i][0] f[i][0]表示走了 i i i步,没有走到 x 2 x_2 x2位置的方案数。
- 状态计算: f [ i ] [ 1 ] + = f [ i − 1 ] [ 0 ] , f [ i ] [ 0 ] + = f [ i − 1 ] [ 0 ] ∗ ( n − 2 ) + f [ i − 1 ] [ 1 ] ∗ ( n − 1 ) f[i][1] += f[i - 1][0], f[i][0] += f[i-1][0]*(n-2) + f[i-1][1]*(n-1) f[i][1]+=f[i−1][0],f[i][0]+=f[i−1][0]∗(n−2)+f[i−1][1]∗(n−1)
对于列也是一样的计算
另外注意精度,不要忘记模
Code:
#include <iostream>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7, MOD = 998244353;
const int N = 1e6 + 10;
int f[N][2], v[N][2], fac[N];
LL qpow(int a, int b) {
int res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
LL C(LL n, LL m) {
return fac[n] % MOD * qpow(fac[m], MOD - 2) % MOD * qpow(fac[n - m], MOD - 2) % MOD;
}
void solve()
{
int n, m, k;
cin >> n >> m >> k;
fac[0] = 1;
for (int i = 1; i <= k; ++ i) {
fac[i] = (fac[i - 1] * i) % MOD;
}
int a, b, c, d;
cin >> a >> b >> c >> d;
if(a != c) f[0][0] = 1;
else f[0][1] = 1;
if(b != d) v[0][0] = 1;
else v[0][1] = 1;
for (int i = 1; i <= k; ++ i) {
f[i][1] = (f[i][1] + f[i - 1][0]) % MOD;
f[i][0] = (f[i][0] + f[i - 1][1] * (n - 1) + f[i - 1][0] * (n - 2)) % MOD;
v[i][1] = (v[i][1] + v[i - 1][0]) % MOD;
v[i][0] = (v[i][0] + v[i - 1][1] * (m - 1) + v[i - 1][0] * (m - 2)) % MOD;
}
int ans = 0;
for (int i = 0; i <= k; ++ i) {
int res = f[i][1] * v[k - i][1] % MOD;
res = (res * C(k, i)) % MOD;
ans = (ans + res) % MOD;
}
cout << ans % MOD << endl;
}
signed main(){
IOS;
int T = 1;
while(T --) solve();
return 0;
}
贪心
Codeforces Round #762 (Div. 3) E
题意:
给出一个序列,每次可以使任意一个数+1. 问使得序列的
M
E
X
MEX
MEX为
0
∼
n
0\sim n
0∼n的最小操作数,不能得到输出-1
sol:
首先,如何第
i
i
i个为-1,那么后面全是-1。假设
M
E
X
MEX
MEX为
i
i
i,那么
0
−
i
−
1
0-i-1
0−i−1一定要有,如果没有一定是从最靠近
i
i
i的值+1获得;那么此时的答案为前
i
−
1
i-1
i−1的操作数+
i
i
i出现的次数
code :
const int N = 2e5 + 10;
int cnt[N];
void solve()
{
int n;
cin >> n;
rep(i, 0, n) cnt[i] = 0;
for (int i = 1;i <= n; ++ i) {
int x; cin >> x;
cnt[x] ++;
}
int res = 0;
vector<int> stk;
rep(i, 0, n) {
if(res == -1) {
cout << res << " \n"[i == n];
continue;
}else {
cout << res + cnt[i] << " \n"[i == n];
}
while(cnt[i] --) {
stk.push_back(i);
}
if(stk.empty()) {
res = -1;
}else {
res += i - stk.back();
stk.pop_back();
}
}
}
二分
Codeforces Round #762 (Div. 3) D
题意:
有
M
M
M,家店
N
N
N个人,需要去
N
−
1
N -1
N−1家店给
N
N
N个人买礼物,使得买的所有礼物的最小值最大。
sol:
二分
code:
void solve()
{
int m, n;
cin >> m >> n;
vector<vector<int>> P(m, vector<int>(n));
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < n; ++ j) {
cin >> P[i][j];
}
}
auto check = [&](int x) -> bool {
vector<bool> vis(n, false);
bool ok = false;
for (int i = 0; i < m; ++ i) {
int cnt = 0;
for (int j = 0; j < n; ++ j) {
cnt += P[i][j] >= x;
vis[j] = vis[j] || (P[i][j] >= x);
}
ok = ok || cnt >= 2;
}
return ok && accumulate(ALL(vis), 0) == n;
};
int l = 0, r = 1e9;
while(l < r) {
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
二维偏序
Atcoder ABC 231 F
题意:
给出两个序列
A
,
B
A, B
A,B,求
A
i
≤
A
j
A_i \le A_j
Ai≤Aj &&
B
i
≥
B
j
B_i \ge B_j
Bi≥Bj的个数.
sol:
二维偏序,注意去重。cdq实现
struct Node {
int x, y, cnt;
bool operator < (const Node &b) const {
if(x == b.x) return y > b.y;
return x < b.x;
}
};
Node a[200010], tmp[200010];
int ans;
void solve()
{
int n;
cin >> n;
rep(i, 1, n) cin >> a[i].x;
rep(i, 1, n) cin >> a[i].y;
sort(a + 1, a + 1 + n);
int nn = n;
n = 1; a[1].cnt = 1;
rep(i, 2, nn) {
if(!(a[i].x == a[i - 1].x && a[i].y == a[i - 1].y)) {
a[ ++ n] = a[i];
a[n].cnt = 1;
}else {
++ a[n].cnt;
}
}
function<void(int, int)> merge = [&](int l, int r) {
if(l >= r) return ;
int mid = l + r >> 1;
merge(l, mid); merge(mid + 1, r);
int cnt = 0;
int i, j, k;
for (i = l, j = mid + 1, k = l; j <= r;) {
while (i <= mid && a[i].y >= a[j].y) {
cnt += a[i].cnt;
tmp[k++] = a[i ++];
}
ans += a[j].cnt * cnt;
tmp[k ++] = a[j ++];
}
while (i <= mid) tmp[k ++] = a[i ++];
for (i = l; i <= r; ++ i) a[i] = tmp[i];
};
merge(1, n);
for (int i = 1; i <= n; ++ i) ans += a[i].cnt * a[i].cnt;
write(ans); br;
}
思维(离线+ dfs+bitset OR 可持久化线段树)
Codeforces Round #368 (Div. 2) D
题意:
有n个书柜,每个书柜有m个防书的地方
四种操作
1 x y 在第x个书柜的第y个位置放一本书
2 x y 拿走在第x个书柜的第y个位置放一本书
3 x 在第x个书柜取反
4 x 回到操作x后的状态
问每次操作后书的数量
sol1: 离线处理 + dfs+Bitset + 思维
q次询问,1-3操作看作
i
−
1
i-1
i−1 ->
i
i
i的一条边,4看作
x
x
x->
i
i
i的一条边
然后按顺序dfs(0)一边的到答案。
code :
struct Q {
int opt, x, y;
};
constexpr int N = 1e5 + 10;
Q query[N];
vector<int> G[N];
bitset<1005> a[1005], full;
void solve()
{
int n, m, q;
cin >> n >> m >> q;
vector<int> Ans(q + 1, 0);
int cnt = 0;
function<void(int u)> dfs = [&](int u) {
bool flag = false;
if(query[u].opt == 1) {
if(a[query[u].x][query[u].y] == 0) {
a[query[u].x][query[u].y] = 1;
++ cnt;
flag = true;
}
} else if(query[u].opt == 2) {
if(a[query[u].x][query[u].y] == 1) {
a[query[u].x][query[u].y] = 0;
-- cnt;
flag = true;
}
} else if(query[u].opt == 3) {
cnt -= a[query[u].x].count();
a[query[u].x] ^= full;
cnt += a[query[u].x].count();
}
// 执行完第u次操作的答案
Ans[u] = cnt;
for (int v : G[u]) dfs(v);
// 回溯, 撤销操作
if(query[u].opt == 1) {
if(flag) {
a[query[u].x][query[u].y] = 0;
cnt --;
}
} else if(query[u].opt == 2) {
if(flag) {
a[query[u].x][query[u].y] = 1;
cnt ++;
}
} else if(query[u].opt == 3) {
cnt -= a[query[u].x].count();
a[query[u].x] ^= full;
cnt += a[query[u].x].count();
}
};
for (int i = 1; i <= m; ++ i) full[i] = 1;
query[0].opt = -1;
for (int i = 1; i <= q; ++ i ) {
int opt, x, y;
cin >> opt;
if(opt == 1) {
cin >> x >> y;
query[i] = {opt, x, y};
G[i - 1].push_back(i);
} else if(opt == 2) {
cin >> x >> y;
query[i] = {opt, x, y};
G[i - 1].push_back(i);
} else if(opt == 3) {
cin >> x;
query[i].opt = opt; query[i].x = x;
G[i - 1].push_back(i);
} else {
cin >> x;
query[i] = {opt , x};
G[x].push_back(i);
}
}
dfs(0);
for (int i = 1; i <= q; ++ i) cout << Ans[i] << "\n";
}
/*
2 3 3
1 1 1
3 2
4 1
*/
sol2: 对于操作四需要实现可持久化,用线段树维护
(待补qwq
code:
在这里插入代码片
计数dp
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
template < typename T> inline void Max(T &a, T b) { if(a < b) a = b; }
template < typename T> inline void Min(T &a, T b) { if(a > b) a = b; }
const double eps = 1e-8;
constexpr int MOD = 998244353;
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
string S; cin >> S;
int N = S.size();
// C(i, j)
vector<vector<long long>> binom(N + 1, vector<long long>(N + 1, 0));
for (int i = 0; i <= N; ++ i) {
binom[i][0] = binom[i][i] = 1;
for (int j = 1; j < i; ++ j) {
binom[i][j] = ( binom[i - 1][j] + binom[i - 1][j - 1] ) % MOD;
}
}
vector<int> cnt(26, 0);
for (auto ch : S) cnt[ch - 'a'] ++ ;
vector<vector<long long>> dp(27, vector<long long>(N + 1, 0));
// dp[i][j] 表示用了前i个字母,长度为j的数量
dp[0][0] = 1;
int sum = 0;
for (int i = 0; i < 26; ++ i) {
for (int j = 0; j <= sum; ++ j) {
for (int k = 0; k <= cnt[i]; ++ k) {
dp[i + 1][j + k] += dp[i][j] * binom[j + k][k] % MOD;
dp[i + 1][j + k] %= MOD;
}
}
sum += cnt[i];
}
LL ans = 0;
for (int i = 1; i <= N; ++ i) {
ans = (ans + dp[26][i]);
}
cout << ans % MOD << endl;
return 0;
}