您的位置:首页 > 其它

BZOJ 2115 异或线性基+DFS找环

2017-09-07 21:22 106 查看
题目链接

题意:给定一个n个点,m条边的无向图,求出一条1−>n节点的路径,使得路径上经过的边的权值的异或和最大。

思路:

解决此题只需要基于一个事实:

任意一条1−>n的路径的异或和都可以由任意一条1−>n的路径和与图中一些环的异或和组合而成。

为什么呢?

简单画个图,举个例子~:

考虑下面的这个图,n=8 m=9,存在3个环,标号1,2,3



可见从1−>8有3条路径。

对于任意的一条,比如说:1−>5−>6−>7−>8

当该路径异或上标号2这个环:1−>5−>6−>7−>4−>2−>1

易发现,路径1−>5−>6−>7异或上了两次,故直接抵消,剩下的路径异或为:

1−>2−>4−>7−>8

刚好为第二条路径。

同理,当该路径异或上标号3这个环,就得到了最下面的1−>n的路径。

故易发现:对于一个经过某路径的环来说,一定是从某个点u出发形成两条路径,最后两条路径又一定会在一个点v相聚,故异或上一个环,就相当于对于u−>v这个过程,走另外的一条路。

那没有经过路径的环呢?比如1−>5−>6−>7−>8和环1。

对于这种情况,我们可以看成我们先到那个环走上一圈,再原路返回,这样从起点到环的路径走了两次,异或后为0,剩下的异或和就只剩环的异或和。

对于上面的情况,异或上环1就相当于我们从1出发,绕环1一圈,回到1之后再走路径1−>5−>6−>7−>8到达终点。

故我们可以找出图中的所有的环的异或和,构建其异或线性基,然后对于一条任意的从1−>n的路径异或和dis[n],从大到小枚举线性基,若能使dis[n]增大,则加上其贡献。

至此,大功告成。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 5e4 + 10;
class Gra{
public:
int v,next;ll w;
}G[A<<2];
int head[A],tot,twt,n,m;
ll dis[A],a[A<<2],b[110];
bool vis[A];

void add(int u,int v,ll w){
G[tot].v = v;
G[tot].w = w;
G[tot].next = head[u];
head[u] = tot++;
}

void dfs(int u,int pre){
vis[u] = 1;
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;ll w = G[i].w;
if(v == pre) continue;
if(!vis[v]){
dis[v] = dis[u] ^ w;
dfs(v,u);
}
else a[++twt] = dis[u]^dis[v]^w;
}
}

void init_Xor(){
for(int i=1 ;i<=twt ;i++) for(int j=62 ;j>=0 ;j--){
if(a[i]>>j & 1){
if(b[j]) a[i] ^= b[j];
else{
b[j] = a[i];
for(int k=j-1 ;k>=0 ;k--) if(b[k] && (b[j]>>k&1)) b[j] ^= b[k];
for(int k=j+1 ;k<=62;k++) if(b[k]>>j&1)           b[k] ^= b[j];
break;
}
}
}
}

int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1 ;i<=m ;i++){
int u,v;ll w;
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dfs(1,1);init_Xor();
for(int i=62 ;i>=0 ;i--) if((dis
^b[i]) > dis
) dis
^=b[i];
printf("%lld\n",dis
);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息