ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

异或相关的杂题

2022-01-28 09:32:02  阅读:198  来源: 互联网

标签:xor int 权值 异或 rm 相关 include 杂题


连续多场 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;
}

标签:xor,int,权值,异或,rm,相关,include,杂题
来源: https://www.cnblogs.com/REKonib/p/15848354.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有