Wannafly挑战赛1-E.cut(线性基+贡献度)

来源:互联网 发布:java乱码怎么解决 编辑:程序博客网 时间:2024/05/17 03:10

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

思路:
  有一个骚操作就是。把点v的所有相连的边的边权异或起来,作为点权。
  那么如果两个相邻点异或一下,则这条边就不被计入了,等价于这条边不在割内。那么任取若干个点权异或,其结果就代表着一种划分方案。
  也就是现在问你n个点,可以产生的不同的异或和的加和是多少。那么可以考虑求出线性基。如果有r个线性基,再考虑到每位的贡献度,如果这一位为1,它对最终的答案的贡献度为2pos×2r1

#include <bits/stdc++.h>using namespace std;const int maxn = 100001 + 5;int v[maxn], pos[50];int main(){    int vs, es;    scanf("%d%d", &vs, &es);    for(int i = 0; i < es; ++i)    {        int x, y, w;        scanf("%d%d%d", &x, &y, &w);        v[x] ^= w, v[y] ^= w;    }    //构造线性基    for(int i = 1; i <= vs; ++i)    {        for(int j = 30; j >= 0; --j)        {            if(v[i] & (1 << j))            {                if(pos[j])  v[i] ^= pos[j];                else                {                    pos[j] = v[i];                    break;                }            }        }    }    int r = 0;    for(int i = 0; i <= 30; ++i) if(pos[i])  ++r;    long long ans = 0;    for(int i = 0; i <= 30; ++i)    {        for(int j = 0; j <= 30; ++j)        {            if(pos[j] & (1 << i))            {                ans += (1 << i);                break;            }        }    }    ans = ans << (r - 1);    while(ans >= 1000000000) ans /= 10;    printf("%lld\n", ans);    return 0;}