版权声明:转载注明出处,谢谢,有问题可以向博主联系 https://blog.csdn.net/VictoryCzt/article/details/84836103
题目地址-CF
- 简略题意
给你一个 个点的图, 条边,每个点有个点权 ,再给你一个 ,对于一个 和一个点集 ,将 中的所有点的点权异或 ,然后对于改变点权后的原图如果没有任何一条边两端端点的点权一样,那么称这个 是合法的,问你总共有多少合法的 , 可以为空集,保证一开始给定的图没有一条边两端端点值一样。
其实我们令一条边的边权为 ,那么肯定对于这条边的两个端点,要么都异或 ,要么都不异或,所以对于一个边权都为 的一个连通块,它只有两种选择,要么都异或 ,要么都不异或 。所以我们将边按照边权排序,然后对于同一种边权,它的方案数就为 ( 为连通块大小, 为当前连通块个数, 为当前在连通块内的点的个数),也就是连通块的选择方案数乘以其它点的选择方案数(异不异或只有两种状态所以为 的多少次方)。然后对于其它不为边权的 ,方案数就是 ,加上即可。
#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=5e5+10;
const ll Mod=1e9+7;
ll n,m,k;
ll C[M],ans;
set <int> rec,kua;//自动去重
int f[M];
int find(int a){return f[a]==a?a:f[a]=find(f[a]);}
void merge(int a,int b){
rec.insert(a);rec.insert(b);
a=find(a);b=find(b);
if(a!=b)f[a]=b;
}
ll fpow(ll a,ll b){
ll ans=1;
for(;b;b>>=1,a=(a*a)%Mod){if(b&1)ans=(ans*a)%Mod;}
return ans;
}
struct edge{
int u,v;ll w;
void in(){scanf("%d%d",&u,&v);w=C[u]^C[v];}
edge(){}
edge(int a,int b,ll c):u(a),v(b),w(c){}
bool operator <(const edge &a)const{return w<a.w;}
}E[M];
void turn_back(){
for(auto a:rec)f[a]=a;rec.clear();//还原要暂存,否则复杂度变成O(nm)了
kua.clear();
}
int main(){
scanf("%I64d%I64d%I64d",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%I64d\n",&C[i]),f[i]=i;
for(int i=1;i<=m;i++)E[i].in();
sort(E+1,E+m+1);
ll last=0,All=fpow(2ll,n);
for(int i=1,j;i<=m;){
ans=(ans+All*((E[i].w-last)%Mod)%Mod)%Mod;//不为边权的x的贡献
last=E[i].w;
for(j=i;j<=m&&E[j].w==last;j++){merge(E[j].u,E[j].v);}
for(j=i;j<=m&&E[j].w==last;j++){
int a=find(E[j].u),b=find(E[j].v);
kua.insert(a);kua.insert(b);
}
i=j;
//rec.size()为连通块内点数,kua.size()为连通块个数
ans=(ans+(fpow(2ll,n+kua.size()-rec.size())%Mod))%Mod;
turn_back();++last;
}
ans=(ans+All*((fpow(2ll,k)-last)%Mod+Mod)%Mod)%Mod;
printf("%I64d\n",ans);
return 0;
}