The 2021 ICPC Asia Shenyang Regional Contest
阿新 • • 發佈:2022-02-05
L. Perfect Matchings
標籤:容斥原理,樹形 $\mathrm{DP}$
如果沒有刪邊的限制就是組合數直接算,但是有一些邊是不能選的.
不妨考慮利用容斥原理來做,欽定選上 $\mathrm{i}$ 條樹邊,然後扣掉點後剩下的邊隨便選.
然後這個就符合二項式反演中的公式,直接反演就能求出 $\mathrm{g[i]}$.
這裡恰好 $\mathrm{i=0}$, 所以直接上容斥的公式即可,時間複雜度是樹形 $\mathrm{DP}$ 的 $\mathrm{O(n^2)}$.
#include <bits/stdc++.h> #define N 4009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; const int mod = 998244353; vector<int>G[N]; int n, size[N], dp[N][N][2], tmp[N][2]; // dp[x][j][0/1]: 當前為 x, 一共選了 j 對,x 是否被匹配了. int ADD(int x, int y) { return (ll)(x + y) % mod; } int DEC(int x, int y) { return (ll)(x - y + mod) % mod; } void dfs(int x, int ff) { size[x] = 1; dp[x][0][0] = 1; for(int i = 0; i < G[x].size() ; ++ i) { int v = G[x][i]; if(v == ff) continue; dfs(v, x); for(int a = 0; a <= size[x] + size[v]; ++ a) tmp[a][0] = tmp[a][1] = 0; for(int a = 0; a <= size[x] / 2; ++ a) { for(int b = 0; b <= size[v] / 2; ++ b) { // 前面貢獻了 a, v 裡面貢獻了 b tmp[a + b][0] = ADD(tmp[a + b][0], (ll)dp[x][a][0] * ADD(dp[v][b][0], dp[v][b][1]) % mod); tmp[a + b][1] = ADD(tmp[a + b][1], (ll)dp[x][a][1] * ADD(dp[v][b][0], dp[v][b][1]) % mod); tmp[a + b + 1][1] = ADD(tmp[a + b + 1][1], (ll)dp[x][a][0] * dp[v][b][0] % mod); } } for(int a = 0; a <= size[x] + size[v]; ++ a) dp[x][a][0] = tmp[a][0], dp[x][a][1] = tmp[a][1]; size[x] += size[v]; } } int qpow(int x, int y) { int tmp = 1; for(; y ; y >>= 1, x = (ll)x * x % mod) { if(y & 1) tmp = (ll)tmp * x % mod; } return tmp; } int get_inv(int x) { return qpow(x, mod - 2); } int calc(int x) { // x 個對. // 2 * x 個點, x 個對的方案數. int tmp = 1, t2 = 1, t3 = 1; for(int i = 1; i <= 2 * x; ++ i) tmp = (ll)tmp * i % mod; for(int i = 1; i <= x; ++ i) t2 = (ll)t2 * i % mod; for(int i = 1; i <= x; ++ i) t3 = (ll)t3 * 2 % mod; return (ll)tmp * get_inv(t2) % mod * get_inv(t3) % mod; } int main() { // setIO("input"); scanf("%d", &n); for(int i = 1; i < 2 * n ; ++ i) { int x, y; scanf("%d%d", &x, &y); G[x].pb(y); G[y].pb(x); } dfs(1, 0); int ans = 0; for(int i = 0; i <= n; ++ i) { int d = (i & 1) ? (mod - 1): 1; ans = ADD(ans, (ll)calc(n - i) * d % mod * ADD(dp[1][i][0], dp[1][i][1]) % mod); } printf("%d\n", ans); return 0; }
M. String Problem
標籤:字串,字尾自動機
正式賽的時候用一個 $\mathrm{lcp}$ 亂搞切掉的,這裡給出正經做法.
遇到字典序問題,考慮利用字尾自動機來解.
靜態問題可以沿著字尾自動機的字元邊貪心走大的.
那麼不妨先維護出字首 $\mathrm{i}$ 的答案,然後考慮下一位的影響.
顯然,如果影響的話一定是下一位的字母被加入答案,那麼在後綴自動機中就是答案中深度最小的點改變.
改變成的新點一定是加入 $\mathrm{i+1}$ 字元時所影響的新點,而所有新點總和是 $\mathrm{O(n)}$ 的.
由於字尾自動機的構建方式,線上做是不現實的,不妨離線構建完字尾自動機然後記錄每個點最早出現位置.
最後開一個數組統計並離線下來即可.
#include <bits/stdc++.h> #define ll long long #define pb push_back #define N 2000009 #define setIO(s) freopen(s".in","r",stdin) using namespace std; char str[N]; int n, last, tot, fir[N], len[N], st[N], ch[N][26], pre[N], dep[N], vis[N], tail, a[N]; struct data { int p, c; data(int p = 0, int c = 0):p(p), c(c){} }; vector<data>G[N]; void extend(int c, int pos) { int np = ++ tot, p = last; len[np] = len[p] + 1, last = np; fir[np] = pos; for(; p && !ch[p][c]; p = pre[p]) { ch[p][c] = np; } if(!p) pre[np] = 1; else { int q = ch[p][c]; if(len[q] == len[p] + 1) pre[np] = q; else { int nq = ++ tot; len[nq] = len[p] + 1; fir[nq] = fir[q]; memcpy(ch[nq], ch[q], sizeof(ch[q])); pre[nq] = pre[q], pre[np] = pre[q] = nq; for(; p && ch[p][c] == q; p = pre[p]) ch[p][c] = nq; } } } int main() { // setIO("input"); scanf("%s", str + 1); n = strlen(str + 1); last = tot = 1; for(int i = 1; i <= n ; ++ i) { extend(str[i] - 'a', i); } for(int i = 1; i <= tot; ++ i) { for(int j = 0; j < 26; ++ j) { if(ch[i][j]) { int q = ch[i][j]; G[fir[q]].pb(data(i, j)); } } } vis[1] = 1, a[++ tail] = 1, st[1] = -1; for(int i = 1; i <= n ; ++ i) { int mk = 0, de = 0; for(int j = 0; j < G[i].size() ; ++ j) { data e = G[i][j]; if(vis[e.p] && e.c > st[e.p]) { if(mk == 0) { mk = e.p, de = dep[e.p]; } else if(dep[e.p] < de) { mk = e.p, de = dep[e.p]; } } } if(mk) { while(a[tail] != mk) vis[a[tail]] = 0, -- tail; st[mk] = str[i] - 'a'; a[++ tail] = ch[mk][str[i] - 'a'], dep[a[tail]] = tail, vis[a[tail]] = 1, st[a[tail]] = -1; } printf("%d %d\n", i - tail + 2, i); } return 0; }