BZOJ 2115 异或线性基+DFS找环

题目链接


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

思路:
解决此题只需要基于一个事实:
任意一条 1 − > n 1->n 1>n的路径的异或和都可以由任意一条 1 − > n 1->n 1>n的路径和与图中一些环的异或和组合而成。

为什么呢?

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

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

这里写图片描述

可见从 1 − > 8 1->8 1>8有3条路径。
对于任意的一条,比如说: 1 − > 5 − > 6 − > 7 − > 8 1->5->6->7->8 1>5>6>7>8
当该路径异或上标号 2 2 2这个环: 1 − > 5 − > 6 − > 7 − > 4 − > 2 − > 1 1->5->6->7->4->2->1 1>5>6>7>4>2>1

易发现,路径 1 − > 5 − > 6 − > 7 1->5->6->7 1>5>6>7异或上了两次,故直接抵消,剩下的路径异或为:
1 − > 2 − > 4 − > 7 − > 8 1->2->4->7->8 1>2>4>7>8
刚好为第二条路径。

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

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

那没有经过路径的环呢?比如 1 − > 5 − > 6 − > 7 − > 8 1->5->6->7->8 1>5>6>7>8和环 1 1 1
对于这种情况,我们可以看成我们先到那个环走上一圈,再原路返回,这样从起点到环的路径走了两次,异或后为 0 0 0,剩下的异或和就只剩环的异或和。

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


故我们可以找出图中的所有的环的异或和,构建其异或线性基,然后对于一条任意的从 1 − > n 1->n 1>n的路径异或和 d i s [ n ] dis[n] dis[n],从大到小枚举线性基,若能使 d i s [ 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[n]^b[i]) > dis[n]) dis[n]^=b[i];
    printf("%lld\n",dis[n]);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值