技巧----异或
异或是一种重要的位运算,关于异或的问题一般有两种思路,一种是01字典树,另一种是线性基。
01字典树
01字典树一般用于解决最值问题,如选两个数异或,求最值。
可通过贪心的策略来寻找与 x异或结果最大的数,即优先找和 x二进制的未处理的最高位值不同的边对应的点,这样保证结果最大。
求最小值,即优先找和 x二进制的未处理的最高位值相同的边对应的点,这样保证结果最小。
可以在一个区间上建两颗01字典树,贪心的求极大值,记录经过节点的次数,用两个不同的数组记录,就可以建在同一颗字典树上了,有删除操作也要记录经过节点次数。
int tol; //节点个数 LL val[32*MAXN]; //点的值 int ch[32*MAXN][2]; //边的值 void init() { //初始化 tol=1; ch[0][0]=ch[0][1]=0; } void add(LL x) { //往 01字典树中插入 x int u=0; for(int i=32;i>=0;i--) { int v=(x>>i)&1; if(!ch[u][v]) { //如果节点未被访问过 ch[tol][0]=ch[tol][1]=0; //将当前节点的边值初始化 val[tol]=0; //节点值为0,表示到此不是一个数 ch[u][v]=tol++; //边指向的节点编号 } u=ch[u][v]; //下一节点 } val[u]=x; //节点值为 x,即到此是一个数 } LL query(LL x) { //查询所有数中和 x异或结果最大的数 int u=0; for(int i=32;i>=0;i--) { int v=(x>>i)&1; //利用贪心策略,优先寻找和当前位不同的数 if(ch[u][v^1]) u=ch[u][v^1]; else u=ch[u][v]; } return val[u]; //返回结果 }
1.HDU 4825(模板题)
题目大意:在一组数中找跟某个数异或结果最大的数。
题解:直接套用模板,将数组中的数插入到 01字典树,对每一个数查询即可。
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define LL long long //#define int LL const int maxn = 32e5+5; const int M = maxn*20; int t,n,q,m,tot; int inf=0x3f3f3f3f3f; int c[maxn][2],a[maxn]; int vis[maxn]; void init(){ tot=0; c[0][0]=c[0][1]=0; } inline add(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(!c[u][v]){ c[++tot][0]=c[tot][1]=0; vis[tot]=0; c[u][v]=tot; } u=c[u][v]; } vis[u]=x; } inline query(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(c[u][!v]) u=c[u][!v]; else u=c[u][v]; } return vis[u]; } inline int Read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-f; ch=getchar();} while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } inline void writeln(int x) { char s[64]; if (x<0) putchar('-'),x=-x; if (!x) putchar('0'); else { int len=0; while (x) s[++len]=x%10+'0',x/=10; while (len) putchar(s[len--]); } putchar('\n'); } int main() { //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("out.out","w",stdout); //t=Read(); scanf("%d",&t); for(int j=1; j<=t; ++j){ init(); //n=Read(),q=Read(); scanf("%d%d",&n,&q); for(int i=0; i<n; ++i) { int x; scanf("%d",&x); add(x); } printf("Case #%d:\n",j); while(q--) { int x; scanf("%d",&x); int ans=query(x); printf("%d\n",ans); } } return 0; }
2.HDU 5536
题目大意:在一个数组中找出 (s[i]+s[j])^s[k] 最大的值,其中 i、j、k 各不相同。
题解:建一颗01字典树,枚举i,j,先将i,j位置上的数从字典树中删除,用和在字典树上找最大值,之后在插入字典树中(数据水,暴力也可做)
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define LL long long //#define int LL const int maxn = 32e3+5; const int M = maxn*20; int t,n,q,m,tot; int inf=0x3f3f3f3f; int c[maxn][2],a[maxn]; int vis[maxn],cnt[maxn]; void init(){ tot=0; c[0][0]=c[0][1]=0; memset(cnt,0,sizeof(cnt)); } inline add(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(!c[u][v]){ c[++tot][0]=c[tot][1]=0; vis[tot]=0; c[u][v]=tot; } u=c[u][v]; cnt[u]++; } vis[u]=x; } void sub01(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; u=c[u][v]; --cnt[u]; } } inline query(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(c[u][!v]&&cnt[c[u][!v]]) u=c[u][!v]; else u=c[u][v]; } return vis[u]^x; } inline int Read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-f; ch=getchar();} while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } inline void writeln(int x) { char s[64]; if (x<0) putchar('-'),x=-x; if (!x) putchar('0'); else { int len=0; while (x) s[++len]=x%10+'0',x/=10; while (len) putchar(s[len--]); } putchar('\n'); } int main() { //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("out.out","w",stdout); //t=Read(); scanf("%d",&t); while(t--){ scanf("%d",&n); init(); for(int i=0; i<n; ++i) {scanf("%d",a+i);add(a[i]);} int ans=-inf; for(int i=0; i<n; ++i) for(int j=i+1; j<n; ++j){ sub01(a[i]),sub01(a[j]); int x=a[i]+a[j]; ans=max(ans,query(x)); add(a[i]),add(a[j]); } printf("%d\n",ans); } return 0; }
题目大意:给你 n 个数,让你求两个不相交的区间元素异或后的和的最大值。
题解:求一遍异或前缀和pre[i]和后缀异或和suf[],先求dp[i](区间1~i中最大的区间异或值) ,dp[i]=max( dp[i-1] , max{ pre[i]^pre[j] }( j < i ) ), 然后就是一般的求ans= max{ dp[i-1] + max{ suf[i]^suf[j] } ( j > i ) }.
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define LL long long //#define int LL const int maxn = 4e5+5; const int M = maxn*32; int t,n,q,m,tot; int inf=0x3f3f3f3f; int c[M][2],a[maxn]; int pre[maxn],suf[maxn],dp[maxn]; int vis[M]; inline void init(){ tot=0; c[0][0]=c[0][1]=0; //memset(cnt,0,sizeof(cnt)); } inline void add(int x){ int u=0; for(int i=32; i>=0; --i){ int v=(x>>i)&1; if(!c[u][v]){ c[++tot][0]=c[tot][1]=0; vis[tot]=0; c[u][v]=tot; } u=c[u][v]; } vis[u]=x; } inline int query(int x){ int u=0; for(int i=32; i>=0; --i){ int v=(x>>i)&1; if(c[u][!v]) u=c[u][!v]; else u=c[u][v]; } return x^vis[u]; } inline int Read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-f; ch=getchar();} while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } inline void writeln(int x) { char s[64]; if (x<0) putchar('-'),x=-x; if (!x) putchar('0'); else { int len=0; while (x) s[++len]=x%10+'0',x/=10; while (len) putchar(s[len--]); } putchar('\n'); } signed main() { //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("out.out","w",stdout); //t=Read(); //scanf("%d",&t); while(~scanf("%d",&n)) { init(); pre[0]=suf[n+1]=dp[0]=0; for(int i=1; i<=n; ++i) { scanf("%d",a+i); pre[i]=pre[i-1]^a[i]; } for(int i=n; i>0; --i) suf[i]=suf[i+1]^a[i]; add(pre[0]); for(int i=1; i<=n; ++i){ dp[i]=max(dp[i-1],query(pre[i])); add(pre[i]); } init(); int ans=0; //cout<<ans<<endl; add(0); for(int i=n; i>0; --i){ ans=max(ans,dp[i-1]+query(suf[i])); add(suf[i]); } printf("%d\n",ans); } return 0; }
4.POJ 3764
题目大意:在树上找一段路径(连续)使得边权相异或的结果最大。
题解:选一个节点为根rt,dfs求出其他节点到该节点路径的异或值,之后就转化为求任选两个数的最大异或和了
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define LL long long //#define int LL const int maxn = 2e5+5; const int M = maxn*32; struct node { //链式前向星节点 int to,next,val; //相邻节点,下一节点,边权 } mp[maxn*2]; int cnt,head[maxn]; //链式前向星 int t,n,q,m,tot=0; int inf=0x3f3f3f3f; int c[M][2],a[maxn]; int vis[M]; void add_edge(int u,int v,int w) { //加边 mp[cnt].to=v; mp[cnt].val=w; mp[cnt].next=head[u]; head[u]=cnt++; } void dfs(int u,int pre,int val){ a[u]=val; for(int i=head[u]; i!=-1; i=mp[i].next){ //int v=mp[i].to; if(mp[i].to!=pre){ int tmp=val^mp[i].val; dfs(mp[i].to,u,tmp); } } } inline void init(){ tot=0; vis[0]=0; memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); c[0][0]=c[0][1]=0; //memset(cnt,0,sizeof(cnt)); } inline void add(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(!c[u][v]){ c[++tot][0]=c[tot][1]=0; vis[tot]=0; c[u][v]=tot; } u=c[u][v]; } vis[u]=x; } inline int query(int x){ int u=0; for(int i=31; i>=0; --i){ int v=(x>>i)&1; if(c[u][!v]) u=c[u][!v]; else u=c[u][v]; } return x^vis[u]; } inline int Read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-f; ch=getchar();} while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } inline void writeln(int x) { char s[64]; if (x<0) putchar('-'),x=-x; if (!x) putchar('0'); else { int len=0; while (x) s[++len]=x%10+'0',x/=10; while (len) putchar(s[len--]); } putchar('\n'); } signed main() { ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("out.out","w",stdout); //t=Read(); //scanf("%d",&t); //memset() while(~scanf("%d",&n)) { init(); cnt=0; for(int i=1; i<=n-1; ++i) { int x,y,val; scanf("%d%d%d",&x,&y,&val); add_edge(x,y,val),add_edge(y,x,val); } tot=0; dfs(0,0,0); init(); add(0); int ans=-inf; //for(int i=0; i<n-1; ++i) cout<<a[i]<<" "; //cout<<endl; for(int i=0; i<n; ++i){ ans=max(ans,query(a[i])); add(a[i]); } printf("%d\n",ans); } return 0; }
#include<bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int t,n,m,tot,MLV; int c[maxn*32][2],vis[maxn*32],a[maxn],cnt[2][maxn*32]; inline void init(){ tot=0; c[0][1]=c[0][0]=vis[0]=0; //c[0][1]=c[0][0]=vis[1][0]=0; //memset(cnt,0,sizeof(cnt)); } inline void add(int pos,int x){ int u=0; for(int i=29; i>=0; --i){ int v=(x>>i)&1; if(!c[u][v]){ c[++tot][0]=c[tot][1]=vis[tot]=0; c[u][v]=tot; } u=c[u][v]; ++cnt[pos][u]; //cout<<pos<<" "<<cnt[pos][u]<<" "<<u<<endl; } //cout<<endl; vis[u]=x; } inline int query(){ int u1=0,u2=0; for(int i=29; i>=0; --i){ int v1=c[u1][0],v2=c[u1][1],v11=c[u2][0],v22=c[u2][1]; if((cnt[0][v1]) && (cnt[1][v11]) ){ u1=v1,u2=v11; } else if((cnt[0][v2]) && ( cnt[1][v22]) ){ u1=v2,u2=v22; } else { if(cnt[0][v1]) u1=v1; else u1=v2; if(cnt[1][v22]) u2=v22; else u2=v11; //cout<<0<<" "<<1<<endl; } //cout<<u1<<" "<<u2<<" "; --cnt[0][u1],--cnt[1][u2]; //cout<<cnt[0][u1]<<" "<<cnt[1][u2]<<endl; } //cout<<vis[u1]<<" "<<vis[u2]<<"\n\n"; return vis[u1]^vis[u2]; } inline int Read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-f; ch=getchar();} while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } inline void writeln(int x) { char s[64]; if (x<0) putchar('-'),x=-x; if (!x) putchar('0'); else { int len=0; while (x) s[++len]=x%10+'0',x/=10; while (len) putchar(s[len--]); } //putchar('\n'); } int main() { //freopen("1.in","r",stdin); //freopen("out.out","w",stdout); //scanf("%d", &t); t=Read(); while (t--) { init(); //scanf("%d",&n); int ma=-1,x; n=Read(); for(int i=1; i<=n; ++i){ x=Read(); add(0,x); } for(int i=1; i<=n; ++i){ x=Read(); add(1,x); } //for(int i=1; i<=n; ++i) add(1,a[i]); //cout<<cnt[0][0]<<" "<<cnt[1][0]<<endl; for(int i=1; i<=n; ++i) a[i]=query(); sort(a+1,a+n+1); for(int i=1; i<=n; ++i) { writeln(a[i]); if(i==n) putchar('\n');else putchar(' '); } } }