• 异或相关的杂题


    连续多场 CF 都卡在 xor 题,并且以各不相同的方式寄了。不得不恶补一下这方面的处理技巧

    Codeforces 访问有点慢,所以挂洛谷的题目链接

    CF1605D Treelabeling

    E 和 S 在玩博弈游戏, E 先手 S 后手。
    给一棵 \(n\) 个节点的树,节点的值包含 \([1,n]\) 中的每一个值。
    E 先随便选择一个点,占领它(点 \(u\) ), S 只能选择与这个点相邻的,没有被占领的点(点 \(v\) )且这两个点满足 \(u⊕v≤\min(u,v)\)
    \(⊕\) 是异或操作
    现在把树给 E 了, E 想要重新排列节点的值(树的形状不变,只是调换结点的值)来达到这个目的:
    最大化第一轮能够选择的点的数量,在选了这个点之后,E 必赢。
    赢:对手无路可走,你就算赢

    \(u~{\rm xor}~v\le \min(u,v)\) 显然需要处理一下。容易发现它等价于 \(u\)\(v\) 的最高位 1 相同。
    \(n=7\) 为例,我们将节点的值按最高位 1 分个组:\(\{1\},\{2,3\},\{4,5,6,7\}\)

    注意到一个事实:树是二分图。因为可以做二染色。以 \(n=7\) ,两侧分别为 \(3,4\) 个点为例
    \(\{1\},\{2,3\}\) 填充 \(3\) 个点的一边,\(\{4,5,6,7\}\) 自然地对应另一边
    然后我们发现无论选哪个点对方都必败
    这个构造是通用的,因为分出来的组的大小是一堆 2 的整数次幂,而一定有一侧的点不超过 \(\dfrac n2\) 个,可以通过二进制拆分恰好填充

    code
    #include<stdio.h>
    const int N=200010;
    int T,n,cnt,x,y,m[2],a[N],b[N],last[N],v[2][N];
    struct node { int y,pre; }E[N<<1];
    void dfs(int x,int f,int c) { // 染色
        v[c][++m[c]]=x;
        for (int i=last[x]; i; i=E[i].pre)
            if (E[i].y!=f) dfs(E[i].y,x,!c);
    }
    void fill(int x) { // 填充,x 为点数较少的颜色
        for (int i=1,j=0; i<=m[x]; i<<=1)
            if (m[x]&i) {
                for (int k=0; k<i; ++k)
                    a[v[x][++j]]=i+k,b[i+k]=1;
            }
        x=(!x);
        for (int i=1,j=1; i<=m[x]; ++i)
            if (!a[v[x][i]]) {
                while (b[j]) ++j;
                a[v[x][i]]=j,++j;
            }
    }
    int main(){
        scanf("%d",&T);
        while (T--) {
            scanf("%d",&n),cnt=m[0]=m[1]=0;
            for (int i=1; i<=n; ++i)
                a[i]=b[i]=last[i]=0;
            for (int i=1; i<n; ++i)
                scanf("%d%d",&x,&y),
                E[++cnt]={y,last[x]},last[x]=cnt,
                E[++cnt]={x,last[y]},last[y]=cnt;
            dfs(1,0,0),fill(m[0]>m[1]);
            for (int i=1; i<=n; ++i) printf("%d ",a[i]);
            putchar('\n');
        }
    }
    

    CF1615D X(or)-mas Tree

    给定一棵 \(n(2\le n\le2\times10^5)\) 个点的带权树,一些边的权值已经确定而其它的没有确定(用 \(-1\) 表示)。
    现在有 \(m(1\le m\le2\times10^5)\) 条信息,表示 \(u_i\)\(v_i\) 的路径上的权值 xor 和的 popcount 为奇数还是偶数。
    试构造出这样一棵树,或者输出无解。

    首先注意到 popcount ,对这东西来说每个二进制位是等价的,所以我们可以尝试把一个数直接压成一位 0/1
    最直接的想法是进行二进制分解,然后把每一位都 xor 起来。结果是 \({\rm popcount}(x)\bmod 2\)
    简单观察一下,发现这步操作似乎非常正确,现在问题转化为:
    一棵树,边的权值为 0/1 ,一些边权未知,给出 \(m\) 条路径上的权值 xor 和,试构造一棵符合条件的树。

    当时想到这里就不会了,然后寄了,rating-=114514
    实际上只需考虑一个简单技巧:树上差分/前缀和。
    随便选个根节点 \(root\) ,令 \(a_i\) 表示 \(i\)\(root\) 的路径上的权值 xor 和。
    那么 \(u\)\(v\) 的路径上的权值 xor 和就是 \(a_u~{\rm xor}~a_v\)
    正确性显然,\({\rm LCA}(u,v)\)\(root\) 的路径走了两遍,贡献自行抵消了。
    是众所周知的 trick 吧,,就我到现在才听说 /youl

    然后每条信息给出的其实就是 \(a_u=a_v\)\(a_u\ne a_v\) 中的一个关系,已知权值的边同理
    并且 \(a_i\) 只有 0/1 两种取值
    写个带权并查集就没了

    code
    #include<stdio.h>
    const int N=200010;
    int T,f,n,m,u[N],v[N],w[N],a[N],fa[N];
    int find(int x) {
        if (fa[x]==x) return x;
        int t=fa[x]; fa[x]=find(t),a[x]^=a[t];
        return fa[x];
    }
    int _union(int x,int y,int z) { //带权并查集
        z=(__builtin_popcount(z)&1);
        int fx=find(x),fy=find(y);
        fa[fy]=fx,a[fy]=(a[x]^a[y]^z);
        return a[fx];
    }
    
    int work() {
        scanf("%d%d",&n,&m),f=0;
        for (int i=1; i<=n; ++i) fa[i]=i,a[i]=0;
        for (int i=2; i<=n; ++i) {
            scanf("%d%d%d",u+i,v+i,w+i);
            if (~w[i]) f|=_union(u[i],v[i],w[i]);
        }
        while (m--) {
            scanf("%d%d%d",u+1,v+1,w+1);
            if (~w[1]) f|=_union(u[1],v[1],w[1]);
        }
        for (int i=1; i<=n; ++i) find(i); //跑一遍路径压缩
        return f;
    }
    int main() {
        scanf("%d",&T);
        while (T--) if (!work()) {
            puts("YES");
            for (int i=2; i<=n; ++i)
                printf("%d %d ",u[i],v[i]),
                printf("%d\n",(~w[i])?w[i]:(a[u[i]]^a[v[i]]));
        }
        else puts("NO");
        return 0;
    }
    

    CF1625D Binary Spiders

    从数列 \(\{a_n\}\) 中选出一些数,使其中任意一对数 \(x,y\) 都满足 \(x~{\rm xor}~y\ge k\)
    给出任意一种满足要求且选出的数最多的方案,或输出无解。

    考虑 01 trie 。
    显然较高位不同的数相互独立。这里较高位指 \({\rm highbit}(k)\) 以上(不含)的二进制位。
    一组较高位相同的数中最多只能选出两个。用 01 trie 判一下就好。
    注意细节问题。

    code
    #include<stdio.h>
    #include<ctype.h>
    #include<algorithm>
    #include<map>
    #define rep(i,l,r) for (int i=l; i<=r; ++i)
    #define add(x) ans[++m]=x
    const int N=300010;
    int n,m,cnt,k,hb,x,y,f,a[N],ans[N],T[N<<5][2];
    void ins(int x) { // [01 trie板子] 插入
        x&=(1<<hb)-1;
        for (int i=hb-1,p=0; ~i; --i) {
            int &t=T[p][(x>>i)&1];
            t||(t=(++cnt)),p=t;
        }
    }
    int qry(int x) { // [01 trie板子] 查询最大异或和(一数为x)
        int s=0; x&=(1<<hb)-1;
        for (int i=hb-1,p=0; ~i; --i) {
            int t=(x>>i)&1;
            if (T[p][!t]) s|=(1<<i),p=T[p][!t];
            else p=T[p][t];
        }
        return s;
    }
    
    std::map<int,int>mp;
    int read() {
        int x=0; char ch=getchar();
        while (!isdigit(ch)) ch=getchar();
        while (isdigit(ch)) x=x*10+(ch^48),ch=getchar();
        return x;
    }
    int main() {
        n=read(),k=read();
        if (!k) {
            printf("%d\n",n);
            rep(i,1,n) printf("%d ",i);
            return 0;
        }
        rep(i,1,n) mp[a[i]=read()]=i;
        std::sort(a+1,a+n+1);
        hb=32-__builtin_clz(k); printf("%d\n",hb);
        rep(i,1,n) if ((y=a[i]>>hb)==x) {
                if (f) continue;
                int t=qry(a[i]);
                if (t>=k) add(a[i]),add(a[i]^t),f=1; //选两个
                ins(a[i]);
            }
            else {
                if (cnt&&!f) add(a[i-1]); //选一个
                rep(i,0,cnt) T[i][0]=T[i][1]=0;
                f=0,cnt=0,ins(a[i]),x=y;
            }
        if (cnt&&!f) add(a[n]);
        if (m<=1) return puts("-1"),0;
        printf("%d\n",m);
        rep(i,1,m) printf("%d ",mp[ans[i]]);
        return 0;
    }
    

    CF1628C Grid Xor

    给一个 \(n\times n\) 的网格,每个点上有一个数 \(a_{i,j}\)
    定义 \(b_{i,j}=a_{i-1,j}~{\rm xor}~a_{i,j-1}~{\rm xor}~a_{i,j+1}~{\rm xor}~a_{i+1,j}\)
    即上下左右四个相邻数的异或和,如果在边上就不算越界的数(或者认为外围有一圈 0 )
    给出 \(b\) ,求 \(a\) 中所有数的异或和。

    神奇构造题,方法很多,但我一个都没想到 /kk

    下面是一种方法

    \(f_{i,j}\) 表示 \(a_{i,j}\) 参与异或的次数。我们只需在 \(b\) 中选取一些合适的元素异或起来,使所有 \(f_{i,j}\) 都是奇数。
    我们从左上到右下把网格扫一遍,如果当前 \(f_{i,j}\) 为偶数,就选择 \(b_{i+1,j}\) ,并且更新它能影响到的四个 \(f\)
    听起来很暴力,我们似乎只保证了前 \(n-1\) 行的 \(f_{i,j}\) 都是奇数,然而这个构造确实是对的

    证明需要考虑一个事:在第一行任意选择一些 \(b_{1,j}\) ,对于后 \(n-1\) 行都一定会有解。

    来看这样一个选择方案:

    ..X.....
    .X.X....
    X.X.X...
    .X.X.X..
    ..X.X.X.
    ...X.X.X
    ....X.X.
    .....X..
    

    构造了一个倾斜 \(45\) 度的 X 矩阵。
    每个 . 都恰与偶数个 X 相邻。
    对于所有标 X 的位置,如果没选 \(b_{i,j}\) ,改为选;如果选了,改为不选。
    对一个可行解进行如上变换,一定会得到另一个可行解,并且第一行特定位置的数的选择情况改变(图中为 \(b_{1,3}\)
    题目保证一定有解,我们通过适当的变换,就可以使第一行处于任意选择情况,并且仍有可行解。

    总之意思就是第一行可以瞎选,反正后面能搞出解
    然后我们发现第一行瞎选之后第二行的方案是确定的,因为 \(f_{1,j}\) 为偶数时必须选 \(b_{2,j}\) 才能补救,反之亦然
    然后第二行也确定了,以此类推可以选完所有行
    根据前面的证明,这种看似乱搞的选法就一定是可行解

    代码就非常简单了

    code
    #include<stdio.h>
    #define rev(x) x=(!x)
    const int N=1010;
    int T,n,ans,a[N][N]; bool f[N][N];
    int main() {
        scanf("%d",&T);
        while (T--) {
            scanf("%d",&n),ans=0;
            for (int i=1; i<=n; ++i)
                for (int j=1; j<=n; ++j)
                    scanf("%d",&a[i][j]),
                    f[i][j]=0;
            for (int i=2; i<=n; ++i)
                for (int j=1; j<=n; ++j)
                    if (!f[i-1][j])
                        ans^=a[i][j],
                        //rev(f[i-1][j]), 
                        rev(f[i][j-1]),
                        rev(f[i][j+1]),
                        rev(f[i+1][j]);
            printf("%d\n",ans);
        }
        return 0;
    }
    
  • 相关阅读:
    bzoj3160(FFT+回文自动机)
    bzoj2555(后缀自动机+LCT)
    luogu P4299 首都
    Annihilate(SA)
    [FJOI2016]神秘数(脑洞+可持久化)
    [ZJOI2016]大森林(LCT)
    bzoj3756pty的字符串(后缀自动机+计数)
    UVa 11582 Colossal Fibonacci Numbers! (斐波那契循环节 & 快速幂)
    UVa 12563 Jin Ge Jin Qu hao (0-1背包)
    UVa 437 The Tower of Babylon (DAG最长路)
  • 原文地址:https://www.cnblogs.com/REKonib/p/15848354.html
Copyright © 2020-2023  润新知