CF#507-C-Network Safety-题解

本文解析了一道CF上的图论题目,涉及点权异或、边权计算及连通块方案数的算法。通过边权排序,计算同边权连通块的方案数,最终求得合法(x,S)的总数。

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

题目地址-CF

  • 简略题意

给你一个 n n n个点的图, m m m条边,每个点有个点权 c i c_i ci,再给你一个 k , k ∈ [ 0 , 60 ] k,k\in[0,60] k,k[0,60],对于一个 x , x ∈ [ 1 , 2 k − 1 ] x,x\in[1,2^k-1] x,x[1,2k1]和一个点集 S S S,将 S S S中的所有点的点权异或 x x x,然后对于改变点权后的原图如果没有任何一条边两端端点的点权一样,那么称这个 ( x , S ) (x,S) (x,S)是合法的,问你总共有多少合法的 ( x , S ) (x,S) (x,S) S S S可以为空集,保证一开始给定的图没有一条边两端端点值一样。


其实我们令一条边的边权为 w i = c v i   x o r   c u i w_i=c_{v_i}\ xor\ c_{u_i} wi=cvi xor cui,那么肯定对于这条边的两个端点,要么都异或 w i w_i wi,要么都不异或,所以对于一个边权都为 w i w_i wi的一个连通块,它只有两种选择,要么都异或 w i w_i wi,要么都不异或 w i w_i wi。所以我们将边按照边权排序,然后对于同一种边权,它的方案数就为 ∑ i = 1 t o t 2 s z e i × 2 n − c n t \sum_{i=1}^{tot}{2^{sze_i}\times 2^{n-cnt}} i=1tot2szei×2ncnt s z e i sze_i szei为连通块大小, t o t tot tot为当前连通块个数, c n t cnt cnt为当前在连通块内的点的个数),也就是连通块的选择方案数乘以其它点的选择方案数(异不异或只有两种状态所以为 2 2 2的多少次方)。然后对于其它不为边权的 x x x,方案数就是 2 n 2^n 2n,加上即可。

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值