A. 寿司晚宴

暴力 dp 是设 $f_{S_1,S_2}$ 表示两个人选的质因子的集合,转移直接预处理每个数的集合转移。

这种题肯定是按照 $\sqrt n$ 分类讨论:对于 $\leq \sqrt n$ 的质数只有 $8$ 个,先状压,然后将其他的数字,如果最大的质因子 $> \sqrt n$ 就按照这个分类。显然每一类如果要选肯定都只会选给同一个人,所以对每一类先预处理 $g_{S}$ 表示那 $8$ 个质数的状态为 $S$ 的方案数,这里复杂度是 $O(n2^8)$ 的,然后再合并起来,复杂度是 $O(n3^8)$(可能是)。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 500+5;
const int B = 8;
int n,ha;

inline void add(int &x,int y){
    x += y;if(x >= ha) x -= ha;
}

bool p[MAXN];
int prime[MAXN],cnt;

inline void prework(){
    FOR(i,2,MAXN-1){
        if(!p[i]) prime[++cnt] = i;
        for(int j = 1;j <= cnt && i*prime[j] < MAXN;++j){
            p[i*prime[j]] = 1;
            if(!(i%prime[j])) break;
        }
    }
}

int g[MAXN][(1<<B)+3],f[2][(1<<B)+3][(1<<B)+3];
int h[2][(1<<B)+3];
bool del[MAXN];
int zt[MAXN];

int main(){
    prework();
    scanf("%d%d",&n,&ha);
    FOR(i,1,B){
        for(int j = prime[i];j <= n;j += prime[i]) zt[j] |= (1<<(i-1));
    }
    FOR(i,B+1,cnt){
        if(prime[i] > n) break;
        // printf("%d\n",prime[i]);
        std::vector<int> S;
        for(int j = prime[i];j <= n;j += prime[i]) S.pb(j),del[j] = 1;
        // for(auto x:S) printf("%d ",x);puts("");
        int now = 0;CLR(h[now],0);
        h[now][0] = 1;
        for(auto x:S){
            CLR(h[now^1],0);
            FOR(S,0,(1<<B)-1){
                if(!h[now][S]) continue;
                add(h[now^1][S],h[now][S]);
                add(h[now^1][S|zt[x]],h[now][S]);
            }
            now ^= 1;
        }
        // DEBUG(h[now][1]);
        // exit(0);
        FOR(S,0,(1<<B)-1) g[i][S] = h[now][S];
    }
    int now = 0;f[now][0][0] = 1;
    FOR(i,2,n){
        if(del[i]) continue;
        CLR(f[now^1],0);
        FOR(S1,0,(1<<B)-1){
            FOR(S2,0,(1<<B)-1){
                if(S1&S2) continue;
                if(!(S2&zt[i])) add(f[now^1][S1|zt[i]][S2],f[now][S1][S2]);
                if(!(S1&zt[i])) add(f[now^1][S1][S2|zt[i]],f[now][S1][S2]);
                add(f[now^1][S1][S2],f[now][S1][S2]);
            }
        }
        now ^= 1;
    }
    FOR(i,B+1,cnt){
        if(prime[i] > n) break;
        CLR(f[now^1],0);
        FOR(S1,0,(1<<B)-1){
            FOR(S2,0,(1<<B)-1){
                if(S1&S2) continue;
                if(!f[now][S1][S2]) continue;
                int S = ((1<<B)-1)^S1;
                for(int T = S;;T=(T-1)&S){
                    // assert((T&S1)==0);
                    add(f[now^1][S1][S2|T],1ll*f[now][S1][S2]*g[i][T]%ha);
                    if(!T) break;
                }
                S = ((1<<B)-1)^S2;
                for(int T = S;;T=(T-1)&S){
                    // assert((T&S2)==0);
                    add(f[now^1][S1|T][S2],1ll*f[now][S1][S2]*g[i][T]%ha);
                    if(!T) break;
                }
                add(f[now^1][S1][S2],ha-f[now][S1][S2]);
            }
        }
        now ^= 1;
    }
    int ans = 0;
    FOR(S1,0,(1<<B)-1) FOR(S2,0,(1<<B)-1) if(!(S1&S2)) add(ans,f[now][S1][S2]);
    printf("%d\n",ans);
    return 0;
}

B.【ZJOI 2016】线段树

神仙 dp 题。。一开始就想不到。

首先考虑最终的序列肯定是若干个极长区间,我们枚举 $x$ 算值 $x$ 的贡献,我们考虑每种情况的每个位置只在极长区间被算到,设 $f_{i,l,r}$ 表示考虑了 $i$ 次操作,区间 $[l,r]$ 是一段 $\leq x$ 的极长区间的方案数(也就是 $a_{l-1} > x,a_{r+1} > x$)。转移的时候考虑下一次操作 $[ll,rr]$ ,分类讨论:

  • $[ll,rr]$ 被 $[l,r]$ 完全包含或无交:不影响这段极长区间,设这样的区间个数为 $g(l,r)$,转移 $f_{i,l,r}\times g(l,r) \to f_{i+1,l,r}$
  • $[ll,rr]$ 覆盖了 $[l,r]$ 的左端点:转移 $f_{i,l,r}\times (l-1) \to f_{i+1,rr+1,r}$
  • $[ll,rr]$ 覆盖了 $[l,r]$ 的右端点:转移 $f_{i,l,r} \times (n-r) \to f_{i+1,l,ll-1}$

最后询问的时候设 $h_{i,j}$ 表示 $i$ 位置 $\leq j$ 的方案数,也就是 $h_{i,j} = \sum_{l \leq i \leq r} f_{j,l,r}$,设权值排序后是 $v_i$,位置 $p$ 的答案就是 $\sum_{i=1}^n (h_{p,i}-h_{p,i-1})v_i$。

转移部分用差分优化可以做到总复杂度 $O(n^3q)$。

首先我们可以拆贡献,我们发现考虑排名为 $x$ 的值时,每个区间 $[l,r]$ 对位置 $p$ 的贡献是 $a_x-a_{x+1}$。所以我们可以每次 dp 完 $x$ 之后答案改成 $\sum_{i=1}^n h_{p,i}(v_i-v_{i+1})$。

我们发现转移其实是和枚举的 $x$ 无关,考虑是否能优化掉这个 $x$ 。

发现这个 $h$ 的转移和答案的统计都是线性的,所以我们只需要将初值的 $1$ 改成 $v_i-v_{i+1}$ ,转移,就能得到所有的 $x$ 的贡献和了。这个做法还是很妙的。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int ha = 1e9 + 7;
const int inv2 = 500000004;
const int MAXN = 400+5;
int n,q;
int a[MAXN];
std::vector<int> S;

inline void add(int &x,int y){
    x += y;if(x >= ha) x -= ha;
}

int f[2][MAXN][MAXN];// 用了 i 次操作,极长区间[l,r]
int row[MAXN][MAXN],col[MAXN][MAXN],g[MAXN][MAXN];
int ans[MAXN];

inline int C(int n){
    return 1ll*n*(n+1)%ha*inv2%ha;
}

int main(){
    scanf("%d%d",&n,&q);
    FOR(i,1,n) scanf("%d",a+i),S.pb(a[i]);
    std::sort(all(S));S.erase(std::unique(all(S)),S.end());
    FOR(i,1,n) a[i] = std::lower_bound(all(S),a[i])-S.begin()+1;
    int now = 0;
    FOR(mx,1,(int)S.size()){
        std::vector<int> ps;ps.pb(0);
        int gx = S[mx-1];if(mx != (int)S.size()) add(gx,ha-S[mx]);
        FOR(i,1,n) if(a[i] > mx) ps.pb(i);
        ps.pb(n+1);
        FOR(i,1,(int)ps.size()-1){
            int l = ps[i-1]+1,r = ps[i]-1;
            if(l <= r) add(f[now][l][r],gx);
        }
    }
    FOR(l,1,n) FOR(r,l,n) g[l][r] = ((C(l-1)+C(r-l+1))%ha+C(n-r))%ha;
    FOR(ccc,1,q){
        CLR(f[now^1],0);
        CLR(row,0);CLR(col,0);
        FOR(l,1,n){
            FOR(r,l,n){
                if(!f[now][l][r]) continue;
                add(f[now^1][l][r],1ll*f[now][l][r]*g[l][r]%ha);
                int gx = 1ll*f[now][l][r]*(n-r)%ha;
                // FOR(k,1,r-1) add(f[now^1][l][k],gx);
                add(row[l][1],gx);add(row[l][r],ha-gx);
                gx = 1ll*f[now][l][r]*(l-1)%ha;
                // FOR(k,l+1,n) add(f[now^1][k][r],gx);
                add(col[r][l+1],gx);add(col[r][n+1],ha-gx);
            }
        }
        FOR(i,1,n) FOR(j,1,n+1) add(row[i][j],row[i][j-1]),add(col[i][j],col[i][j-1]);
        now ^= 1;
        FOR(l,1,n) FOR(r,l,n) add(f[now][l][r],row[l][r]),add(f[now][l][r],col[r][l]);
    }
    FOR(l,1,n){
        FOR(r,l,n){
            add(ans[l],f[now][l][r]);
            add(ans[r+1],ha-f[now][l][r]);
        }
    }
    FOR(i,1,n) add(ans[i],ans[i-1]),printf("%d%c",ans[i]," \n"[i==n]);
    return 0;
}

C. Alice和Bob又在玩游戏

这是个套路题。。

首先不同连通块相当于若干个子游戏,也就是 $SG$ 值的异或。我们考虑一个连通块的怎么求。

我们设 $i$ 的子树的的 $SG$ 值为 $sg_i$,每次枚举一个点,删掉这些点后就变成了若干个连通块,也就是若干个子问题,对所有的分支可能都取 $\text{mex}$,设 $son_v$ 表示 $v$ 子树内的点的集合,$anc(v)$ 表示 $v$ 到当前子树的根的点的集合,可以得到转移:

$$ sg_i = \text{mex}_{v \in son}\{ \text{XOR}_{x \in anc(v),y \in son_x} sg_y \} $$

记 $s_v = \text{XOR}_{x \in anc(v),y \in son_x} sg_y $,我们观察一下每次子树往上合并的时候的变化:相当于每个子树内的点异或上了除了这个子树对应的儿子的其他的儿子的 $sg$ 值的异或和。

相当于要支持查询一个集合的 $\text{mex}$ ,整体 $\text{XOR}$ 一个值,合并。和 $\text{XOR}$ 这种问题有关我们想到了 trie。

求 $\text{mex}$ 类似线段树二分,记录一个 $sz$ 表示子树内的点的大小就可以了。合并类似线段树合并,$\text{XOR}$ 直接打标记。

对 01trie 还不是很熟练调了很长时间。。回来多写点这样的题吧

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 1e5 + 5;
const int MAXM = 16;
const int M = 2e6 + 5;

int n,m;
struct Edge{
    int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt;

inline void add(int u,int v){
    e[++cnt] = (Edge){v,head[u]};head[u] = cnt;
    e[++cnt] = (Edge){u,head[v]};head[v] = cnt;
}

// 支持插入 合并 整体xor 求mex

int tag[M],rt[MAXN],ch[M][2],tot,dep[M],sz[M];
#define lc (ch[x][0])
#define rc (ch[x][1])

inline void pushdown(int x){
    if(tag[x]){
        if(lc) tag[lc] ^= tag[x];
        if(rc) tag[rc] ^= tag[x];
        if((tag[x]>>dep[x])&1) std::swap(lc,rc);
        tag[x] = 0;
    }
}

inline void insert(int o,int val){
    if(!rt[o]) rt[o] = ++tot;
    int x = rt[o];dep[x] = MAXM;
    ROF(i,MAXM,0){
        int c = (val>>i)&1;
        pushdown(x);
        if(!ch[x][c]) ch[x][c] = ++tot,dep[tot] = i-1;
        x = ch[x][c];
    }
    if(!sz[x]){
        x = rt[o];
        ROF(i,MAXM,0){
            int c = (val>>i)&1;
            pushdown(x);
            sz[x]++;
            x = ch[x][c];
        }
        sz[x]++;
    }
}

inline int query(int x){
    if(!x || dep[x] == -1) return 0;
    pushdown(x);
    // DEBUG(x);DEBUG(dep[x]);
    if(sz[lc] < (1<<(dep[x]))) return query(lc);
    else return (1<<dep[x])|query(rc);
}
bool flag = 0;
inline int merge(int x,int y){
    if(!x || !y) return x+y;
    pushdown(x);pushdown(y);
    lc = merge(lc,ch[y][0]);
    rc = merge(rc,ch[y][1]);
    if(dep[x] != -1) sz[x] = sz[lc]+sz[rc];
    // if(flag){
        // DEBUG(x);
        // DEBUG(lc);DEBUG(rc);
    // }
    return x;
}

int sg[MAXN];
bool vis[MAXN];

inline void print(int x,int num=0){
    if(dep[x] == -1){
        DEBUG(num);
        // printf("%d\n",num);
        return;
    }
    pushdown(x);
    if(lc) print(lc,num);
    if(rc) print(rc,num|(1<<dep[x]));
}

inline void dfs(int v,int fa=0){
    vis[v] = 1;int sm = 0;
    for(int i = head[v];i;i = e[i].nxt){
        if(e[i].to == fa) continue;
        dfs(e[i].to,v);
        sm ^= sg[e[i].to];
    }
    // if(v == 19)flag = 1;
    for(int i = head[v];i;i = e[i].nxt){
        if(e[i].to == fa) continue;
        tag[rt[e[i].to]] ^= sm^sg[e[i].to];
        // if(v == 19){
            // DEBUG(sz[38]);
            // print(rt[e[i].to]);
            // DEBUG(rt[v]);
        // }
        
        rt[v] = merge(rt[v],rt[e[i].to]);
    }
    // if(v == 19) flag = 0;
    insert(v,sm);
    // if(v == 17){
        // DEBUG(query(rt[v]));
        // DEBUG(sz[v]);
        // DEBUG(sz[rt[v]]);
        // DEBUG(rt[v]);
        // print(rt[v]);
    // }
    // if(v == 19){
        // DEBUG(query(rt[v]));
        // DEBUG(sz[v]);
    // }
    sg[v] = query(rt[v]);
    // if(v == 19){
        // DEBUG(sg[v]);
    // }
}

inline void Solve(){
    scanf("%d%d",&n,&m);
    FOR(i,1,m){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);
    }
    int SG = 0;
    // dfs(1);
    // FOR(i,1,n) DEBUG(sg[i]);
    // exit(0);
    FOR(i,1,n) if(!vis[i]) dfs(i),SG ^= sg[i];
    // FOR(i,1,n) DEBUG(sg[i]);
    // DEBUG(SG);
    puts(SG?"Alice":"Bob");
    cnt = 0;FOR(i,1,n) head[i] = 0;
    FOR(i,1,tot) tag[i] = ch[i][0] = ch[i][1] = dep[i] = sz[i] = 0;
    FOR(i,1,n) rt[i] = 0,vis[i] = 0;tot = 0;
    // exit(0);
}

int main(){
    int T;scanf("%d",&T);
    while(T--) Solve();
    return 0;
}
Last modification:October 15th, 2020 at 09:59 pm
如果觉得我的文章对你有用,请随意赞赏