1. 程式人生 > 其它 >The 2021 ICPC Asia Shenyang Regional Contest

The 2021 ICPC Asia Shenyang Regional Contest

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; 
}