nowcode Cut(Wannafly挑战赛1 E)

题目描述


给定一个无向简单图(即无重边无自环). 每条边都有一个权值. 这个图的一个鸽, 指的是将它的点集划分为两个不重不漏的集合S和T. 这个鸽的权值, 是所有两个端点分别属于S和T的边的权值的异或和(即, S内部的边和T内部的边都不算). 现在问这个图的鸽的所有可能权值的和是多少. 由于这个数很大, 只需要输出前9位, 不足9位则全部输出.

输入描述:
第一行两个数n和m表示图的点数和边数(0<n<100001,0<m<200001).
之后m行每行3个数表示一条边的两个端点和这条边的权值. 点的编号从1到n,权值为0到10^9之间的整数.

输出描述:
输出答案
示例1

输入
2 1
1 2 1
输出

1
说明

所有的鸽中,只有把1和2分开的鸽才有非0的权值, 这个权值只能是1. 所以所有可能的权值的和是0+1=1.

参考大神:http://www.cnblogs.com/wmrv587/p/7667243.html

分析:

       比赛题解为:一个割的权值就是集合中点权值的异或(点权值就是和他相邻的边权值的异或。)(集合取割的两个集合中哪一个都行。)理由是这样的话集合内的边恰好算了0或者2次,集合之间的算了一次。这样就转化成一些数字随便选一些异或起来的可能答案和,就很好统计了。

       ?????怎么求答案和????

      还要去学一波 线性基!

      大神说:先求出线性基,设秩为r

  枚举每一位,看看是否有某个基该位是1,如果有1,那么这些基能异或出的2^r个数中,该位是1的一定有一半,所以该位的贡献就是$2^{r-1}*2^i$

     怎么理解呢,就是 把 r 个线性基 组合 起来的异或值 在 该 i 位上为1的组合 一定有 2^(r-1)种。

     为什么?  设 r 个基中 i位为1的有 a个,为0的有b个,既 a+b=r, 那么异或值i位为1的条件就是 从 a中选奇数个与b随便组合:

     既: 2^b*{C(a,1)+C(a,3)+C(a,5)+... }= 2^b* 2^(a-1) = 2 ^(a+b-1) = 2^(r-1)//  不知道证明是否有错 ,有望指点。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
const int maxn = 100000;
typedef long long ll;
using namespace std;
int n,m,a[maxn+5];
int pos[32];
//线性基 + 求贡献。
//参考大神代码:https://www.nowcoder.com/acm/contest/view-submission?submissionId=19070357
//大神的 博客:http://www.cnblogs.com/wmrv587/p/7667243.html
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
    {
        int x,y,w;
        scanf("%d %d %d",&x,&y,&w);
        a[x]^=w, a[y]^=w;
    }
    for(int i=1; i<=n; i++)//求线性基
    {
        for(int j=30; j>=0; j--)
        {
            if(a[i]&(1<<j))
            {
                if(!pos[j])
                {
                    pos[j] = a[i];
                    break;
                }
                a[i]^=pos[j];
            }
        }
    }
    int num = 0;
    for(int j=0;j<=30;j++) if(pos[j]) num++;
    num--;
    ll ans = 0;
    for(int j=0;j<=30;j++)// 每一位 对答案的贡献。
    {
        int flag = 0;
        for(int i=0;i<=30;i++) if(pos[i]&(1<<j)) flag = 1;
        if(flag) ans+=(1ll<<j);
    }
    ans<<=num;
    while (ans>=1000000000) ans/=10;
    printf("%lld\n",ans);
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_32944513/article/details/78317651