异或运算相关算法小结
异或运算相关
整理了一些与异或(xor)有关的题目及算法
What is xor?
对于两个整数\(a\)和\(b\),记数\(x\)的第\(i\)位为\(x_i\),则
在c++中,^代表xor运算,即\(a\ xor \ b=a\text^ b\)
异或有什么用呢
- 强制在线
- 出毒瘤题
异或有什么性质呢
-
一个数异或0还得到这个数,一个数异或1等于给它取反
显然 -
异或相当于二进制意义下的不进位加法
手玩即可证明 -
\(a\text^ a=0\)
这一点性质十分重要
比如说维护\(\sum a[i]\ xor \ a[i+1]\ \ xor\ \cdots\ xor\ a[j]\)时,我们可以求出其前缀异或和\(sum[i]=a[1]\ xor\ a[2]\ xor\cdots\ xor\ a[i]\),那么原式就变成\(sum[j]\ xor \ sum[i-1]\),方便维护
然后这个玩意再发展一下
在一棵树上,边有边权,记\(dis(u,v)\)为\(u\)到\(v\)路径上的边权的异或和,那么就有\(dis(u,v)=dis(1,u)\text^dis(1,v)\)(下文将用^代替xor)
proof:记\(LCA(u,v)=lca\),则\(dis(u,v)=dis(u,lca)\text^dis(lca,1)\text^dis(v,lca)\text^dis(lca,1)=dis(1,u)\text ^dis(1,v)\)
这与普通的树上路径求和的形式一致
异或的应用
入门例题
大意:给定长度不超过\(10^5\)的序列\(a\),求有多少对\((l,r)\)使得\(a_l\text ^a_{l+1}\text^ \cdots \text^a_{mid}=a_{mid+1}\text^ a_{mid+2}\text^\cdots\text^ a_r\)
其中\(mid=\frac{r-l+1}{2}\)且\(r-l+1\)为偶数
分析:异或值相等等价于两者异或和为0
即\(sum_{l-1}\text^sum_r=0\)
在处理前缀异或和的同时开个桶分奇偶统计相同的值的个数即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxd=1000000007,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
int n,a[300500],cnt[2200500][2],sum[300500];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
n=read();int i;
for (i=1;i<=n;i++) a[i]=read();
for (i=1;i<=n;i++) sum[i]=sum[i-1]^a[i];
//for (i=1;i<=n;i++) cout << sum[i] << " ";cout << endl;
ll ans=0;
for (i=0;i<=n;i++)
{
ans+=cnt[sum[i]][i&1];
cnt[sum[i]][i&1]++;
}
cout << ans << endl;
return 0;
}
异或的常见套路
1)按位统计贡献
从异或定义出发,根据序列中的数的相同位上二进制数的取值统计对答案的贡献
[Luogu3917] 异或序列
分析:转化为求\(\sum_{0\leq i\leq j\leq n}sum_i\text^sum_j\)
求出\(sum\)之后考虑所有\(sum\)的相同位对答案的贡献
即在第\(i\)为贡献位\(cnt_0*cnt_1*2^i\)(\(cnt_{0/1}\)表示二进制表示上第\(i\)位为0/1的数的个数)
暴力统计即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
int n,a[100100],sum[100100];
ll cnt[2][40];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
n=read();
rep(i,1,n) {a[i]=read();sum[i]=sum[i-1]^a[i];}
rep(i,0,30)
{
rep(j,0,n)
{
cnt[((sum[j]>>i)&1)][i]++;
}
}
ll ans=0;
rep(i,0,30)
ans+=cnt[0][i]*cnt[1][i]*(1ll<<i);
printf("%lld",ans);
return 0;
}
bzoj2400
这辈子都不可能有权限号的
很明显对于每一位而言答案是互相独立的,于是考虑对于每一位进行考虑
设当前枚举到第\(i\)位
我们考虑这样的网络流模型转化:保留原图的双向边并设容量为1,在所有已经确定值的位置上,假设该值的第\(i\)位为1,那么由\(S\)向该点连向一条容量为\(INF\)的边,反之则向\(T\)连一条容量为\(INF\)的边
考虑题目中所说的权值,由异或的定义知,只有一个为\(0\)且另一个为\(1\)时才会对答案有贡献
那么现在要这个权值最小,我们可以考虑最小割,因为由建图知最后与\(S\)相连的点的值为\(1\),与\(T\)相连的点的值为\(0\),且一开始与\(S\)和\(T\)相连的边不可能被割开,所以被割开的边就意味着对答案产生了\(2^i\)的贡献,最小化这个贡献就是求这个网络的最小割
于是对点权的每一位都做一次最小割就能求出答案
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define int long long
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
struct edgenode{
int u,v;
}edge[100100];
struct flownode{
int to,nxt,flow;
}sq[500500];
int n,all=1,head[600],a[600],cur[600],ans[1010],ans2=0,dep[600],s,t,m;
bool vis[600];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void add(int u,int v,int flow1,int flow2)
{
all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].flow=flow1;head[u]=all;
all++;sq[all].to=u;sq[all].nxt=head[v];sq[all].flow=flow2;head[v]=all;
}
bool bfs()
{
memset(vis,0,sizeof(vis));
memset(dep,0,sizeof(dep));
queue<int> q;
q.push(s);vis[s]=1;
while (!q.empty())
{
int u=q.front();q.pop();
int i;
for (i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if ((sq[i].flow) && (!vis[v]))
{
vis[v]=1;q.push(v);
dep[v]=dep[u]+1;
}
}
}
if (!vis[t]) return 0;
rep(i,1,t) cur[i]=head[i];
return 1;
}
int dfs(int now,int t,int lim)
{
if ((!lim) || (now==t)) return lim;
int i,sum=0;
for (i=cur[now];i;i=sq[i].nxt)
{
int v=sq[i].to;cur[now]=i;
if (dep[v]==dep[now]+1)
{
int f=dfs(v,t,min(lim,sq[i].flow));
if (f)
{
lim-=f;sum+=f;
sq[i].flow-=f;
sq[i^1].flow+=f;
if (!lim) break;
}
}
}
return sum;
}
int dinic()
{
int ans=0;
while (bfs()) ans+=dfs(s,t,maxd);
return ans;
}
void findans(int u,int one)
{
if (u!=s) ans[u]+=one;
vis[u]=1;
int i;
for (i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if ((!vis[v]) && (sq[i].flow)) findans(v,one);
}
}
signed main()
{
int T=read();
while (T--)
{
n=read();m=read();
rep(i,1,m) {edge[i].u=read();edge[i].v=read();}
int q=read();
memset(a,-1,sizeof(a));
memset(ans,0,sizeof(ans));
rep(i,1,q)
{
int pos=read(),val=read();
a[pos]=val;
}
s=n+1;t=n+2;
rep(i,0,30)
{
memset(head,0,sizeof(head));all=1;
rep(j,1,m) add(edge[j].u,edge[j].v,1,1);
rep(j,1,n)
{
if (a[j]<0) continue;
if ((a[j]>>i)&1) add(s,j,maxd,0);
else add(j,t,maxd,0);
}
dinic();
memset(vis,0,sizeof(vis));
findans(s,1<<i);
}
rep(i,1,n) printf("%lld\n",ans[i]);
}
return 0;
}
注意spoj和bzoj对题目的要求有一点细微的区别
多测不清空,爆零两行泪
2)线性基
对这里就是补充例题的
还是补充一点:线性基求第\(k\)大异或和
最简单的想法是将\(k\)二进制拆分,然后对于某一个为1的位上异或其对应的线性基中的数
如果我们还运用原来的线性基,可能会出现一些bug,因为在线性基中的某个数\(p_i\)只保证了其最高位为1,而对于剩下的位数是没有限制的
这个时候我们需要将原来的线性基进行一些转化,使得其中的每一个数的二进制表示有且仅有一个1
大概就是下面这样
rep(i,0,30)
{
per(j,i-1,0)
{
if ((b[i]>>j)&1) b[i]^=b[j];
}
}
rep(i,0,30) if (b[i]) now[++cnt]=b[i];
bzoj2844
翻译:给定一个长度为\(n\)的序列,将其的\(2^n\)个子集中的异或值从小到大排序,询问\(p\)在其中的排名
这个题和上面正好倒过来了,同样将线性基拆分一下,然后从小到大遍历新线性基,分别进行异或操作,如果当前异或之后得到的值比查询的值要小的话就乘上一个二的次幂
然后还有一个问题:这是针对已经求好线性基的情况,在给出的序列中会有一些被线性基舍弃的数,这也就造成了最终得到的\(2^n\)个数可能出现重复的情况
我们记被舍弃的数有\(cnt\)个,对于每一个被舍弃的数\(x_i\),线性基中都有对应的一个序列\(p_1,p_2,\cdots,p_{m_i}\)使得\(x_i\text^p_1\text^p_2\text^\cdots\text^p_{m_i}=0\),故对于某个可以被线性基表示的数,如果我们要求强制选择\(x_i\)的话,我们可以再选取\(p_1,p_2,\cdots,p_{m_i}\)以消除\(x_i\)的影响(如果它们与原表示中有相同的数的话可约去)
于是答案乘上\(2^{cnt}\)即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 10086
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
int n,a[100100],b[100100],now[100100],cnt0=0,cnt=0;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void insert(int x)
{
per(i,30,0)
{
if ((x>>i)&1)
{
if (!b[i]) {b[i]=x;return;}
else x^=b[i];
}
}
if (!x) cnt0++;
}
ll qpow(ll x,int y)
{
ll sum=1;
while (y)
{
if (y&1) sum=(sum*x)%maxd;
x=(x*x)%maxd;
y>>=1;
}
return sum;
}
int main()
{
n=read();
rep(i,1,n) {a[i]=read();insert(a[i]);}
rep(i,0,30)
{
per(j,i-1,0)
{
if ((b[i]>>j)&1) b[i]^=b[j];
}
}
rep(i,0,30) if (b[i]) now[++cnt]=b[i];
ll ans=1;
int val=read();
if (!val) {printf("1");return 0;}
else
{
int num=0;
per(i,cnt,1)
if ((num^now[i])<val) {num^=now[i];ans=(ans+qpow(2,i-1))%maxd;}
}
ans=(ans*qpow(2,cnt0))%maxd;
printf("%lld",(ans+1)%maxd);
return 0;
}
bzoj4568
树上异或的常规套路:对每个点维护根节点到其点上的数的线性基
那么对于\((u,v)\)而言只需要将\((1,u)\)和\((1,v)\)这两个线性基合并即可
线性基合并即暴力插入
献上常数极大的代码,注意边界
// luogu-judger-enable-o2
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define int long long
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
struct node{
int to,nxt;
}sq[40040];
int n,q,all=0,head[20020],dep[20020],fa[20020][17];
ll bas[20020][17][65],now[20020],num[65];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void addedge(int u,int v)
{
all++;sq[all].to=v;sq[all].nxt=head[u];head[u]=all;
}
void add(ll *a,ll x)
{
per(i,61,0)
{
if ((x>>i)&1ll)
{
if (!a[i]) {a[i]=x;break;}
else x^=a[i];
}
}
}
void Merge(ll *a,ll*b)
{
per(i,61,0) if (b[i]) add(a,b[i]);
}
ll query(ll *a)
{
ll ans=0;
per(i,61,0) ans=max(ans,ans^a[i]);
return ans;
}
void dfs(int u,int fu)
{
dep[u]=dep[fu]+1;fa[u][0]=fu;
rep(i,1,15)
{
if (fa[u][i-1])
{
fa[u][i]=fa[fa[u][i-1]][i-1];
Merge(bas[u][i],bas[u][i-1]);
Merge(bas[u][i],bas[fa[u][i-1]][i-1]);
}
}
int i;
for (i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if (v==fu) continue;
dfs(v,u);
}
}
int LCA(int u,int v)
{
if (dep[u]<dep[v]) swap(u,v);
int tmp=dep[u]-dep[v];
per(i,15,0)
{
if ((tmp>>i)&1)
{
Merge(now,bas[u][i]);
u=fa[u][i];
}
}
if (u==v) return u;
per(i,15,0)
{
if (fa[u][i]!=fa[v][i])
{
Merge(now,bas[u][i]);Merge(now,bas[v][i]);
u=fa[u][i];v=fa[v][i];
}
}
Merge(now,bas[u][0]);Merge(now,bas[v][0]);
return fa[u][0];
}
void init()
{
n=read();q=read();
rep(i,1,n) {scanf("%lld",&num[i]);add(bas[i][0],num[i]);}
rep(i,1,n-1)
{
int u=read(),v=read();
addedge(u,v);addedge(v,u);
}
dfs(1,0);
}
void work()
{
while (q--)
{
memset(now,0,sizeof(now));
int u=read(),v=read(),lca=LCA(u,v);
Merge(now,bas[lca][0]);
printf("%lld\n",query(now));
}
}
signed main()
{
init();
work();
return 0;
}
3)01trie
线性基的优势在于:将一个长度可能是\(10^5\)甚至更大的集合的异或值值域压缩至集合中最大数的位数阶级个数(一般不超过\(60\)个),从而较大的优化了时间,简化了操作难度
但是也有一个致命的缺点:不灵活。无法应对某些强制选取某一个数的询问
那么这个时候我们可以考虑另一个处理异或问题的利器——01trie
普通的trie是将一条边看做一个字母,而01trie可以看做一颗二叉树——某个点只有两条出边(虽然你不一定会真正的把它们都建出来),分别代表0和1
比较经典的问题就是给出数\(x\),在序列中找一个数使得两者异或和最大
我们先将该序列的01trie建出来,建01trie主要利用的是每个数的二进制表示,从高到低建trie即可
然后我们在trie上暴力跑\(x\),比如\(x\)在第\(i\)位上是\(p_i\),那么我们就看能不能往\(p_i\text^1\)的地方走,由于我们是从高到低处理的,故这样的贪心总是正确的
codeforces842D
翻译:luogu
板子题,注意到整体异或可以开一个\(sum\)累计当前进行整体异或的数的异或和然后暴力在trie树上询问即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
typedef long long ll;
const int maxd=1000000007,N=100000;
const double pi=acos(-1.0);
int tot=1,n,q,a[1001000],trie[5005000][2],cnt[5005000][2];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void insert(int x)
{
int now=1;
per(i,19,0)
{
int tmp=(x>>i)&1;
if (!trie[now][tmp]) trie[now][tmp]=(++tot);
cnt[now][tmp]++;now=trie[now][tmp];
}
}
int query(int num)
{
int now=1,ans=0;
per(i,19,0)
{
int tmp=(num>>i)&1;
if (cnt[now][tmp]<(1<<i)) now=trie[now][tmp];
else {ans|=(1<<i);now=trie[now][tmp^1];}
if (!now) return ans;
}
return ans;
}
int main()
{
n=read();q=read();
rep(i,1,n) a[i]=read();
sort(a+1,a+1+n);
n=unique(a+1,a+1+n)-a-1;
rep(i,1,n) insert(a[i]);
int now=0;
rep(i,1,q)
{
int x=read();
now^=x;
printf("%d\n",query(now));
}
return 0;
}
十二省联考2019D1T1 异或粽子
结合NOI2010 超级钢琴食用更佳
将NOI的题的“和”改成“异或和”就是联考了(还要卡常还好开O2)
使用前缀异或和将问题转化为“给出一个长\(n+1\)的序列\(0,a_1,\cdots,a_n\),将其两两做异或,求前\(k\)大的异或和
我么想一想在超级钢琴里面我们做了什么:对每一个位置维护了其第\(k\)大的和,并将其丢入堆中,每次取出堆顶元素,统计答案并将相同位置的\(k+1\)大和放入堆中,区间第\(k\)大主席树维护即可
这一题完全相同,我们将主席树换成可持久化01trie即可
但是我不会可持久化01trie啊
没事下面有教程
我们考虑去掉“查询的第\(k\)大一定在当前元素之前”的这个限制
那么对于一个异或和\(x\text^y\),由交换律可知它会被两次得到
并且由堆维护的最大值可知这两个值一定是连续出现的,否则就一定存在这一个值比它还大,取那个值会更优
于是我们做\(2*k\)次询问即可
还有:这题严重卡常(反正考场开O2对吧QAQ)
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
struct trienode{
int ch[2];
}trie[18000100];
struct node{
int pos,k;ll val;
};
bool operator <(const node &p,const node &q)
{
return p.val<q.val;
}
priority_queue<node> q;
int n,k,tot=1,siz[18000100];
ll sum[500100];
void insert(ll x)
{
int now=1;siz[now]++;
per(i,31,0)
{
int tmp=((x>>i)&1);
if (!trie[now].ch[tmp]) trie[now].ch[tmp]=(++tot);
now=trie[now].ch[tmp];siz[now]++;
}
}
ll query(ll x,int k)
{
int now=1;ll ans=0;
per(i,31,0)
{
int tmp=((x>>i)&1);
if (!trie[now].ch[tmp^1]) now=trie[now].ch[tmp];
else if (siz[trie[now].ch[tmp^1]]>=k) {ans+=(1ll<<i);now=trie[now].ch[tmp^1];}
else {k-=siz[trie[now].ch[tmp^1]];now=trie[now].ch[tmp];}
}
return ans;
}
ll read()
{
ll x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
n=read();k=read();
ll ans=0;
rep(i,1,n) {ll x=read();sum[i]=sum[i-1]^x;}
rep(i,0,n) insert(sum[i]);
rep(i,0,n) q.push((node){i,1,query(sum[i],1)});
rep(i,1,2*k)
{
node now=q.top();q.pop();
if (i%2==0) ans+=now.val;
if (now.k==n) continue;
q.push((node){now.pos,now.k+1,query(sum[now.pos],now.k+1)});
}
printf("%lld",ans);
return 0;
}
4)可持久化01trie
假设我们的问题比上面的基础例子还强怎么办?
即现在的询问是给定\(l\ r\ x\),询问在\(l\text~r\)的区间中找一个\(p\)使得\(x_p\)最大
我们借鉴主席树的思想,维护\(1\text~i\)的01trie,然后利用\(size\)的大小暴力询问即可
对就是主席树写成了01trie的形式
前缀异或和转化不必说
问题转为\(sum_N\text^sum_{p-1}\text^x\)最大
注意\(p\)的范围是\([l,r]\),但我们查询的是\(p-1\)
当然如果你把\(0\)放在第一位的话也无妨
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
struct trienode{
int cnt,ch[2];
}trie[15005000];
int n,q,sum=0,tot=0,root[15000600];
char op[5];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void insert(int &id,int pre,int num,int pos)
{
id=(++tot);trie[id]=trie[pre];trie[id].cnt++;
if (pos<0) return;
int tmp=(num>>pos)&1;
insert(trie[id].ch[tmp],trie[pre].ch[tmp],num,pos-1);
}
int query(int pre,int now,int num,int pos)
{
if (pos<0) return 0;
int tmp=(num>>pos)&1,
cnt=trie[trie[now].ch[tmp^1]].cnt-trie[trie[pre].ch[tmp^1]].cnt;
if (cnt>0) return (1<<pos)+query(trie[pre].ch[tmp^1],trie[now].ch[tmp^1],num,pos-1);
else return query(trie[pre].ch[tmp],trie[now].ch[tmp],num,pos-1);
}
int main()
{
n=read();q=read();
insert(root[1],root[0],0,24);n++;
rep(i,2,n) {int x=read();sum^=x;insert(root[i],root[i-1],sum,24);}
rep(i,1,q)
{
scanf("%s",op);
if (op[0]=='A')
{
int x=read();sum^=x;n++;
insert(root[n],root[n-1],sum,24);
}
else if (op[0]=='Q')
{
int l=read(),r=read(),x=read();x^=sum;
printf("%d\n",query(root[l-1],root[r],x,24));
}
}
return 0;
}
TJOI2018 xor
树剖+可持久化trie
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
struct trienode{
int ch[2],cnt;
}trie[4030000];
int tot=0,root[100100];
struct edgenode{
int to,nxt;
}sq[200200];
int all=0,head[100100];
int tim=0,dfn[100100],noww[100100],siz[100100],dep[100100],fa[100100],
son[100100],tp[100100];
int n,w[100100],q;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void add(int u,int v)
{
all++;sq[all].nxt=head[u];sq[all].to=v;head[u]=all;
}
void dfs1(int u,int fu)
{
dep[u]=dep[fu]+1;fa[u]=fu;siz[u]=1;
int maxsiz=0,i;
for (i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if (v==fu) continue;
dfs1(v,u);siz[u]+=siz[v];
if (maxsiz<siz[v]) {son[u]=v;maxsiz=siz[v];}
}
}
void dfs2(int u,int tpu)
{
dfn[u]=(++tim);noww[tim]=w[u];tp[u]=tpu;
if (!son[u]) return;
dfs2(son[u],tpu);
int i;
for (i=head[u];i;i=sq[i].nxt)
{
int v=sq[i].to;
if ((v==fa[u]) || (v==son[u])) continue;
dfs2(v,v);
}
}
void modify(int &id,int pre,int val,int d)
{
id=(++tot);trie[id]=trie[pre];trie[id].cnt++;
if (d<0) return;
int tmp=(val>>d)&1;
modify(trie[id].ch[tmp],trie[pre].ch[tmp],val,d-1);
}
int query(int pre,int now,int val,int d)
{
if (d<0) return 0;
int tmp=(val>>d)&1,
cnt=trie[trie[now].ch[tmp^1]].cnt-trie[trie[pre].ch[tmp^1]].cnt;
if (cnt) return (1<<d)+query(trie[pre].ch[tmp^1],trie[now].ch[tmp^1],val,d-1);
else return query(trie[pre].ch[tmp],trie[now].ch[tmp],val,d-1);
}
int main()
{
n=read();q=read();
rep(i,1,n) w[i]=read();
rep(i,1,n-1)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,1);
rep(i,1,n) modify(root[i],root[i-1],noww[i],30);
while (q--)
{
int op=read();
if (op==1)
{
int x=read(),y=read();
printf("%d\n",query(root[dfn[x]-1],root[dfn[x]+siz[x]-1],y,30));
}
else if (op==2)
{
int x=read(),y=read(),z=read();
int ans=0;
while (tp[x]!=tp[y])
{
if (dep[tp[x]]<dep[tp[y]]) swap(x,y);
ans=max(ans,query(root[dfn[tp[x]]-1],root[dfn[x]],z,30));
x=fa[tp[x]];
}
if (dep[x]>dep[y]) swap(x,y);
ans=max(ans,query(root[dfn[x]-1],root[dfn[y]],z,30));
printf("%d\n",ans);
}
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步