【BZOJ5303】【HAOI2018】—反色游戏(结论+tarjan割点)

本文深入探讨了图论中的一种特定算法,重点分析了如何通过理解图的联通性和节点特性来解决特定问题。文章详细解释了一个关键结论:仅当图中存在包含偶数个节点的联通块时,才可能找到解决方案。此外,还提供了算法的具体实现步骤,包括如何通过改变边的状态来构造解决方案,以及如何计算不同情况下的方案数量。
摘要由CSDN通过智能技术生成

传送门

出题人的题解:

有个显然的结论,只有一个联通块中有偶数个节点才有解
为什么呢?我们将黑点任意两两配对,对一对黑点考虑它们之间的任意一条路径,将路径上的边的状态(操作or不操作)取反,立即可以得到一种合法方案,

那显然在一棵树上的方案数只有1种
考虑一个联通块的非树边,显然是可以替换掉树上的一条路径的
那非树边的选或不选就对应了不同的点集,则方案数就是 2 m − n + 1 2^{m-n+1} 2mn+1
而对于一个图,方案数就是 2 m − n + p , p 2^{m-n+p},p 2mn+p,p是联通块个数

那对于删去一个点就很简单了,考虑一下删去对所在联通块和黑点数目的影响分类讨论就可以了

注意特判一个点的情况

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
	while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=getchar();
	return res*f;
}
const int N=100005;
const int mod=1000000007;
int n,m,adj[N],nxt[N<<1],to[N<<1],in[N<<1],pow2[N],cnt;
int dfn[N],low[N],tot,cut[N],siz[N],vis[N],bel[N],belnum,sub[N];
char s[N];
inline void addedge(int u,int v){
	nxt[++cnt]=adj[u],adj[u]=cnt,to[cnt]=v;
}
inline void clear(){
	memset(adj,0,sizeof(adj));
	memset(dfn,0,sizeof(dfn));
	memset(siz,0,sizeof(siz));
	memset(sub,0,sizeof(sub));
	memset(cut,0,sizeof(cut));
	memset(in,0,sizeof(in));
	cnt=tot=0;
}
void tarjan(int u,int fa){
	low[u]=dfn[u]=++tot;
	vis[u]=1,siz[u]=s[u]=='1';
	bel[u]=belnum;
	for(int e=adj[u];e;e=nxt[e]){
		int v=to[e];
		if(!dfn[v]){
			tarjan(v,u);
			siz[u]+=siz[v];
			if(low[v]>=dfn[u]){
				++cut[u],vis[u]&=siz[v]%2==0;
				sub[u]+=siz[v];
			}
			else low[u]=min(low[u],low[v]);
		}
		else if(v!=fa){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(!fa)--cut[u];
}
int main(){
	pow2[0]=1;
	for(int i=1;i<N;++i)pow2[i]=pow2[i-1]*2%mod;
	int T=read();
	while(T--){
		n=read(),m=read();
		clear();
		for(int i=1;i<=m;++i){
			int u=read(),v=read();
			addedge(u,v),addedge(v,u);
			++in[u],++in[v];
		}
		scanf("%s",s+1);
		int num=0,odd=0;
		for(int i=1;i<=n;++i){
			if(!dfn[i]){
				belnum=i,tarjan(i,0);
				++num,odd+=siz[i]&1;
			}
		}
		int ans=m-n+num;
		cout<<((odd)?(0):(pow2[ans]));
		for(int i=1;i<=n;++i){
			if(!in[i])cout<<" "<<((odd==siz[i])?(pow2[ans]):(0));
			else{
				if(vis[i]&&((siz[bel[i]]-(s[i]=='1')-sub[i])%2==0)&&(odd==(siz[bel[i]]&1)))
					cout<<" "<<(pow2[ans-in[i]+1+cut[i]]);
				else cout<<" "<<0;
			}
		}
		puts("");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值