codeforces 724G. Xor-matic Number of the Graph

博客介绍了codeforces 724G题目的解决方案,涉及无向图的路径异或和问题。通过建立生成树,计算节点到根的异或和,结合线性基的概念解决环的异或和问题,最终实现求解所有有趣三元组的异或和之和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接http://codeforces.com/contest/724/problem/G
题目大意:给出一张n个点m条边的无向图,对于一个三元组(u,v,s),如果1<=u < v<=n,并且存在一条从u到v的路径(可以重复经过点和边)使得路径上的边权异或和为s,那么我们说它是“有趣的”。求所有有趣的三元组中的 s 之和。
数据范围:1 ≤ n ≤ 100 000, 0 ≤ m ≤ 200 000, 0 ≤ 边权 ≤ 10^18

题解:我们假设这是一张连通图,如果不是那么对每个连通块分别求解即可。
考虑两个点 u 和 v ( u < v ),任取从 u 到 v 的两条路径 p0 和 p1(可以经过相同的边但不能完全相同),得到两个三元组 (u,v,s0) 和 (u,v,s1),路径 p0 和 p1 组合就形成了一个环,假设这个环上的边权异或和为s2,则s2=s0 xor s1,即s1=s0 xor s2。由于p0和p1可能有边相同,所以这个环实际上是由若干个不经过重复的边的环组成。所以我们发现,任意一条从u到v的路径都可以表示成s0 xor 若干个环。
假设我们已经知道了一条从u到v的路径,那么是否对于任意一个环,这条路径与它组合后都会是一条合法的u到v的路径呢?答案是肯定的。因为这是一个连通图,我们只要从u走到环上的任意一个点w,绕环一圈然后原路返回u再从u走到v,则从u到w的路径经过两次,不影响异或的和,于是我们得到了一条由原路径加上一个新的环组成的路径。

得出结论:两个点u和v之间的所有路径都可以由它们之间的某条路径异或图中的任意多个环得到。

所以我们可以求出图中的一棵生成树,计算出每个点到根的异或和f[ i ],那么任意两个点u和v之间的一条路径都可以由f[u] xor f[v]得到(因为从根到它们的公共祖先的路径异或了两次)。对于所有不在环上的边(ui,vi,ti),f[ui] xor f[vi] xor ti 即是图中的一个环。这样我们就得到了图中任意两点之间的一条路径和图中所有的环。

现在的问题是如何求出两个点u和v之间的所有不同的异或和。
首先,我们假设一个数列a1,a2,…an中取其中任意个数可以组成的异或和称为这个数列的值域。有以下结论:对于数列中的任意一个数ai,将所有aj(j≠i)的值变成aj xor ai,该数列的值域不变。证明:假设我们取了原数列的k个数s1=ap1 xor ap2 xor … xor apk,那么新数列对应的k个数的异或和s2=s1 xor j个ai,其中 j=k或k-1取决于这k个数中有没有ai。当 j 为偶数时,j 个ai相互抵消,s1=s2;当 j 为奇数时,只要在新数列中把ai再异或一遍,s1=s2 xor ai。

假设图中的环的异或和为数列c,我们需要求出一个数列d,使得它的值域与c相同,并且d中不存在两种不同的取数方案使得这两种方案的异或和相等,即不存在若干个数的异或和为0(据说这玩意叫线性基)。
我们可以用类似高斯消元的方法来求出这个数列。把c中的数看成60位的二进制数,然后从最高位开始,每次找到一个当前位为1的数(之前选过的数不能再选,找不到就跳过做下一位),把这个数选入d数列,然后将数列中(包括已经选入d数列的)所有当前位为1的数异或上这个值,这样对于每一位 i,最多只有一个数的第 i 位为1,因此不存在若干个数的异或和为0(因为对于每一位都没有另一个数和它抵消)。假设最后d数列有cnt个数,那么cnt<=60。这样一来,d数列中的数任意组合都是一种不同方案,共有2^cnt种。
对于任意的 i,如果在d中有第 i 位为1的数,那么d的所有组合中第 i 位为0和1的分别有2^(cnt-1)种,否则全部组合都是0。证明:后一种情况显然,我们只证前一种。假设F(n , 0)和F(n , 1)表示d中有n个数时组合为0和1的方案数。如果只有一个数,那么这个数第 i 位是1,F(1,0)=F(1,1)=1=2^0。每加进去一个数时,如果该数第 i 位是0,那么不管这个数取还是不取,原先的值都没有变,所以F(n,0)=F(n-1,0)*2,F(n,1)=F(n-1,1)*2;如果是1,那么如果不取则不改变,取则改变原先的值,所以F(n,0)=F(n,1)=F(n-1,0)+F(n-1,1)。因此如果存在为1的数,则F(n,0)=F(n,1)=2^(n-1)。

之后我们可以单独考虑每一位。假设g[ i ][ j ] ( j=0,1) 表示所有的点中 f 的值的第 i 位为 j 的个数。如果d中有第 i 位为1的数,那么ans=ans+2^i*2^(cnt-1)*C(n,2),否则ans=ans+2^i*2^cnt*g[i][0]*g[i][1]。

时间复杂度O(m*log10^18)

(ps:英语渣看题解看得快哭出来了,明明每个单词都(通过翻译)懂了但是连起来愣是没看懂。最后盯着前半段看了五六遍才yy出来,写的时候还把当前连通块的点数直接当成n为此wa了4发,感觉自己大概已经是没救了。另:以上题解颇有一些智障证明,觉得显而易见的读者请无视之(你到现在才说都看完了好吗))

代码如下:

#include <algorithm>
#include <cstring>
#include <cstdio>
typedef long long ll;
const int mo=1000000007,N=100005;
int to[N*4],ne[N*4],fi[N*4],u[N],g[60][2],tot=0,num,cnt,ans=0;
long long val[N*4],f[N],c[N*2];
void add(int x,int y,ll z){
    to[++tot]=y;val[tot]=z;ne[tot]=fi[x];fi[x]=tot;
}
void dfs(int fa,int x){
    u[x]=++num;
    for (int i=0;i<60;i++) g[i][(f[x]>>i)&1]++;
    for (int i=fi[x];i;i=ne[i])
    if (!u[to[i]]){
        f[to[i]]=f[x]^val[i];
        dfs(x,to[i]);
    }
    else if (u[to[i]]>u[x])
        c[++cnt]=f[x]^f[to[i]]^val[i];
}
void gauss(){
    int j=1;
    for (int i=59,k;i>=0;i--){
        for (k=j;k<=cnt && !((c[k]>>i)&1);k++);
        if (k>cnt) continue;
        std::swap(c[j],c[k]);
        for (k=1;k<=cnt;k++)
            if (k!=j && ((c[k]>>i)&1)) c[k]^=c[j];
        j++;
    }
    cnt=j-1;
}
void calc(){
    int pow=1,num1,num2=(1ll<<cnt)%mo;
    if (cnt) num1=(1ll<<(cnt-1))%mo;
    for (int i=0;i<60;i++){
        int x=0;
        for (int j=1;!x && j<=cnt;j++) x=(c[j]>>i)&1;
        if (x) ans=(ans+1ll*num*(num-1)/2%mo*num1%mo*pow)%mo;
            else ans=(ans+1ll*g[i][0]*g[i][1]%mo*num2%mo*pow)%mo;
        pow=pow*2%mo;
    }
}
int main(){
    int n,m;
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=m;i++){
        int x,y;ll z;
        scanf("%d%d%I64d\n",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }
    for (int i=1;i<=n;i++)
    if (!u[i]){
        memset(g,0,sizeof(g));
        num=cnt=0;dfs(0,i);
        gauss();calc();
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值