www.lydsy.com/JudgeOnline/problem.php?id=2115 (题目链接)
题意:给出一张图,可能有重边和自环,在图中找出一条从1~n的路径,使得经过的路径的权值的异或和最大,每条边可以重复经过并且重复计算异或和。
Solution
刚看到这道题,想了10分钟完全没有思路,于是就膜了题解。
我们先把图看成一棵树,那么我们所需要找出的路径可以分成一些环和一条从1~n的树上路径,至于树是怎么构造的以及那条从1~n的路径是怎么走的并不重要,因为我们可以通过在总路径上补环来增大异或和。于是这个问题就转化为了如何在环的异或和中选出一些使得这些值异或上从1~n的路径的异或和最大。
于是我们先dfs一遍把环以及它的异或和抠出来,当然这些环可能并不完全,但它们可以通过互相异或来异或出没有得到的环。如果发现当前节点x的目标节点e[i].to已经被走过了,那么这个环的异或和就是dis[x]^dis[e[i].to]^e[i].w。
怎么求最大异或和呢?这需要用到一个很神的东西:线性基
什么是线性基:
若干数的线性基是一组数a1,a2,…an,其中ax的最高位的1在第x位。通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。
线性基主要有2个优秀的性质:
可以O(k)(二进制的位数)的求出能异或出的最大值,或者是判断一个数能否被异或出来。
线性基的构造:
线性基的构造有两种:
第一是高斯消元。通过高斯消元将二进制矩阵消成上三角矩阵或者是对角矩阵,当然消成后者时间复杂度略高,但也更为方便,求解异或和最大时一路for下去就可以了。而上三角矩阵需要判断如果答案异或上这个值能否使答案增大。
第二种就是一种拟阵贪心,详情参见jump大爷的博客——www.cnblogs.com/ljh2000-jump/p/5869991.html,因为证明太高深,这里也就不再赘述。
所以这道题就很好做了对吧。
代码:
// bzoj2115 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define inf 2147483640 #define Pi 3.1415926535898 #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=50010,maxm=100010; struct edge {int to,next;LL w;}e[maxm<<1]; LL dis[maxn],c[maxm<<2],bin[65]; int vis[maxn],n,m,tot,sum,cnt,head[maxn]; void link(int u,int v,LL w) { e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w; e[++cnt].to=u;e[cnt].next=head[v];head[v]=cnt;e[cnt].w=w; } void dfs(int x) { vis[x]=1; for (int i=head[x];i;i=e[i].next) { if (!vis[e[i].to]) { dis[e[i].to]=dis[x]^e[i].w; dfs(e[i].to); } else c[++sum]=dis[x]^dis[e[i].to]^e[i].w; } } void Gauss() { for (LL i=bin[60];i;i>>=1) { int j=tot+1; while (j<=sum && !(c[j]&i)) j++; if (j==sum+1) continue; swap(c[++tot],c[j]); for (j=1;j<=sum;j++) if (j!=tot && c[j]&i) c[j]^=c[tot]; //for (int k=j+1;k<=sum;k++) if (c[k]&i) c[k]^=c[tot]; } } int main() { bin[0]=1;for (int i=1;i<=60;i++) bin[i]=bin[i-1]<<1; scanf("%d%d",&n,&m); for (int u,v,i=1;i<=m;i++) { LL w; scanf("%d%d%lld",&u,&v,&w); link(u,v,w); } dfs(1); Gauss(); LL ans=dis[n]; for (int i=1;i<=tot;i++) ans=max(ans,ans^c[i]); printf("%lld\n",ans); return 0; }