【题解】割cut [牛客Wannafly挑战赛1 E]
传送门:割 ( ext{cut}) [牛客 ( ext{Wannafly}) 挑战赛 ( ext{1 E})]
【题目描述】
给定一个 (n) 个点 (m) 条边的无向简单图(无重边无自环),每条边都有权值 (w) 。图的一个割指:将点集划分为两个不重不漏的集合 (S) 和 (T) 。割的权值为所有两个端点分别属于 (S) 和 (T) 的边的权值异或和(即 (S) 内部的边和 (T) 内部的边不算)。
现求这个图的割的所有可能的权值之和,输出答案前 (9) 位(不足 (9) 位则全部输出)。
【输入输出样例】
样例输入:
2 1
1 2 1
样例输出:
1
【数据范围】
对于 (20 \%:) (nleqslant 20,) (mleqslant 400) 。
另外 (20 \%:) (m=n-1,) 保证读入的图是一棵树。
另外 (20 \%:) (wleqslant 16) 。
对于 (100 \%:) (1leqslant nleqslant 10^5,) (1leqslant mleqslant min(frac{n(n-1)}{2},2 imes10^5),) (0leqslant wleqslant 10^9)
【分析】
这题被放到了今天 ( ext{NOI}) 模拟赛 ( ext{day2 T2})。
样例差评 样例差评 样例差评 样例差评 样例差评
题目描述差评 题目描述差评 题目描述差评 题目描述差评 题目描述差评
题目要求的是割所有可能的权值之和,不是所有割的权值之和!
一开始题目理解错误,头都快想炸了还是没有思路,只差没破口大骂了。
对于 (nleqslant 20) 的部分直接 (2^n) 枚举割的划分方式,然后枚举所有边求权值。复杂度 (2^nmapprox O(4 imes 10^8)),显然会超时(实际效率不清楚,或许常数小能卡过?),考虑把枚举边改为枚举两个点集,于是降为 (sum_{i=1}^{n}C_{n}^{i}i(n-i)approx n^22^{n-2}approx O(10^8)) 。
对于一棵树的情况,发现任意选出一些边砍掉都可以划分成割(因为一定能划成二分图),于是题意变为:给出 (m) 个值,求任选一些值可能的异或和之和。
显然要挂一个线性基。但问题在于如何快速求所有异或和之和,(O(2^{size})) 暴力枚举显然是不行的,考虑按位处理:对于每一位 (i),若存在某个基的第 (i) 位为 (1),那么答案加上 (2^i2^{size-1}) 。
正确性证明:
任选一个第 (i) 位为 (1) 的基 (p) 把它去掉,在剩下的 (size-1) 个基中进行选择,易知有 (2^{size-1}) 种不同的异或和,设异或和第 (i) 位为 (0) 的有 (cnt_0) 种,为 (1) 的有 (cnt_1) 种(显然 (cnt_0+cnt_1=2^{size-1}))。现在把它们都异或上 (p),则又得到 (cnt_0) 种第 (i) 位为 (1) 的,(cnt_1) 种第 (i) 位为 (0) 的。故第 (i) 位为 (1) 的共有 (cnt_0+cnt_1=2^{size-1}) 种。
对于一般的无向简单图,考虑将每个点所连的边权值都异或起来,并作为这个点的点权,那么点集 (S)(或者 (T))的点权异或和就是割的权。
正确性证明:
若边的两个端点在不同点集,那么该边权只会被异或一次,反之会被异或两次消掉(即变为 (0)) 。
于是问题转换为:任选一些点作为点集 (S),计算其可能的点权异或和之和。
发现和上面那个问题一模一样,只是插入线性基的值不同了。
还剩下最后一个问题:怎么输出前 (9) 位?
总不可能现场写一个高精吧.....于是我去问了问教练,发现 ( ext{std}) 直接开 ( ext{ull}) 就过了,珂啪。
时间复杂度为:(O(m+log^{2}inf)) 。
((wleqslant 16) 的部分分是给那些只会 (O(2^{size})) 枚举线性基统计答案的人准备的)
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define LL unsigned long long
#define Re register int
using namespace std;
const int N=1e5+3,M=2e6+3;
int n,m,x,y,z,o=1,head[N];LL ans;
struct QAQ{int w,to,next;}a[M<<1];
inline void add(Re x,Re y,Re z){a[++o].w=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
int f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=f?-x:x;
}
struct JI{
int cnt,p[33];
inline void insert(Re x){
for(Re i=30;i>=0;--i)
if((x>>i)&1){
if(!p[i]){++cnt,p[i]=x;break;}
x^=p[i];
}
}
}ji;
int cnt;
inline void print(LL x){
if(x>9)print(x/10);
putchar(x%10+'0');
if(++cnt>=9)exit(0);
}
int main(){
// freopen("cut.in","r",stdin);
// freopen("cut.out","w",stdout);
in(n),in(m);
for(Re i=1;i<=m;++i)in(x),in(y),in(z),add(x,y,z),add(y,x,z);
for(Re x=1;x<=n;++x){
Re v=0;
for(Re i=head[x];i;i=a[i].next)v^=a[i].w;
ji.insert(v);
}
for(Re i=0;i<=30;++i){
Re flag=0;
for(Re j=0;j<=30;++j)if(ji.p[j])flag|=((ji.p[j]>>i)&1);
if(flag)ans+=(LL)(1<<i)*(1<<(ji.cnt-1));
}
print(ans);
fclose(stdin);
fclose(stdout);
return 0;
}