测试地址:XOR
做法:本题需要用到异或线性基+DFS树+贪心。
这题有很多神奇的结论,本蒟蒻只会感性证明,严谨证明就请各位大佬自己脑补了……
首先我们有一个结论:
结论1:任何一条从到路径的异或和,都可以表示成任何一条从到的路径和几个与该路径在同一连通块中的环的异或和。
感性证明一下,因为是异或和,所以一条边走两次就相当于抵消掉了,那么一条路径最后肯定会抵消成一条路径加几个环的形式,而这条路径本身也可以通过异或上一个和它相交的环的方式形成另一条路径,所以得证。
有了这个结论,并且因为这个图是连通的,我们就可以想到一个算法:先随便找到一条从到的路径,然后把所有环的异或值列出来,通过线性基+贪心的方法找到异或值的最大值。我们有以下结论:
结论2:先求出所有环异或值的线性基,然后从大到小比较,如果当前异或和异或上这个基后比当前异或和大,就异或上这个基,最后得到的肯定是异或和的最大值。
这个是比较经典的线性基贪心了,网上也有很多大佬证过,这里就不证了。
那么我们就得到了一个很优的算法,但我们发现还有一个问题没有解决——我们怎么把所有的环列出来?我们可以先求出图的DFS树,根据DFS树的性质,这个图的非树边都是返祖边(即边的两端必然是祖先和后代的关系),这个属于比较基础的知识,这里就不证了。我们把仅含一条非树边的环称为基本环,我们有以下结论:
结论3:任何环的异或和都可以表示成若干基本环的异或和。
这个大概感性理解一下就能想通,所以我们只用把所有基本环的异或和列出来,这个东西求出来的线性基和求所有环的线性基得出来的结果相同,这样一个DFS就完事了,再结合上述代码,就可以以的时间复杂度解决这个题目了。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxbit=63;
int n,m,first[50010]={0},tot;
ll sum[50010],a[200010],b[70]={0};
bool vis[50010]={0};
struct edge
{
int id,v,next;
ll d;
}e[200010];
void insert(int a,int b,ll d,int id)
{
e[++tot].v=b;
e[tot].d=d;
e[tot].next=first[a];
e[tot].id=id;
first[a]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
tot=0;
for(int i=1;i<=m;i++)
{
int a,b;
ll d;
scanf("%d%d%lld",&a,&b,&d);
insert(a,b,d,i),insert(b,a,d,i);
}
}
void dfs(int v,int laste)
{
vis[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].id!=laste)
{
if (vis[e[i].v]) a[++tot]=sum[v]^sum[e[i].v]^e[i].d;
else sum[e[i].v]=sum[v]^e[i].d,dfs(e[i].v,e[i].id);
}
}
void work()
{
for(int i=1;i<=tot;i++)
for(int j=maxbit;j>=0;j--)
if ((a[i]>>j)&1)
{
if (b[j]) a[i]^=b[j];
else
{
b[j]=a[i];
for(int k=0;k<j;k++)
if (b[k]&&((b[j]>>k)&1)) b[j]^=b[k];
for(int k=j+1;k<=maxbit;k++)
if ((b[k]>>j)&1) b[k]^=b[j];
break;
}
}
ll s=sum[n];
for(int i=maxbit;i>=0;i--)
if (b[i]&&(s^b[i])>s) s^=b[i];
printf("%lld",s);
}
int main()
{
init();
tot=sum[1]=0;
dfs(1,0);
work();
return 0;
}