【CF19E】【bzoj4424】Fairy【二分图判定】【思维分析】

懒得找传送门所以自行百度吧ouo

删掉一条边,使得原图成为一个二分图。

二分图充要条件:无奇环。

一个很美好的二分图:树

二分图:删了一条边还是二分图

所以对于原图,随手dfs一下先构造出一棵树。

然后我们讨论非树边对原图的影响。

我们定义:只有一个非树边的奇环为基环,只有一个非树边的偶环为藕环,所对应的非树边叫基/藕边。

这个定义的优势在于每条边的基藕性是确定的。

然后我们可以轻松地发现,要删去一条边,那么这条边必须是所有奇环的交。

体现在我们的定义下,就是一条边,必须是所有基环的交,但不能是藕环上的边。

为什么呢?

因为图大概长这样:

红色是非树边,棕色是同时存在于基藕环上的边

这条边删去之后,剩下的边构成了一个奇环。因为基环-1+藕环-1=奇数+偶数-2=奇数。所以必然能构成新的奇环。

所以这就是条件了。

总结:

在所有基环上但不在藕环上的边是合法的。

没有基环所有边都是合法的。

一个基环的话,奇环上的边都是合法的。

顺带一提

无向图上dfs出来的非树边只可能是树的返祖边,也就是从下向上的。所以计算两个点之间的边树只需要用深度减一下就星了。

//嘤嘤嘤
//没有♂基♂环爱删不删
//一个♂基♂环删环上边
//多♂基♂环,如果这条边是所有♂基♂环的交,那合法,但不能经过偶环。
//因为这个偶环在删掉这条边后可以和所有残缺的假♂基♂环构成新♂基♂环。
//即偶环-1+♂基♂环-1=奇数。
//考虑所有非树边生成的环影响,对于每条边差分统计环数。判断即可。 
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
struct node{
	int u,v,ji,ou,flag;//记录该边经过♂基♂环数判定是否经过所有 ♂基♂环 
				 	//打标记判定树边或非树边,记偶判定是否过偶。 
}edge[2000003];
int first[2000003],nxt[4000003],to[4000003],tot;
//tot从0开始方便双向边直接右移对应(其实巨佬都是这样做的只是我以前喜欢从1开始) 
void add(int a,int b){
	nxt[++tot]=first[a];first[a]=tot;to[tot]=b;
}
int ji[2000003],ou[2000003];//差分记录父边所属♂基♂偶环数
int n,m,vis[2000003],dep[2000003]; 
void dfs(int u,int dp,int fa){//假dp嘤嘤嘤 
	dep[u]=dp;vis[u]=1;//cout<<u<<" "<<fa<<endl;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(vis[v])continue;
	//	cout<<u<<" "<<v<<" "<<edge[(i%2)?((i+1)/2):(i/2)].u<<" "<<edge[(i%2)?((i+1)/2):(i/2)].v<<endl;
		edge[(i%2)?((i+1)/2):(i/2)].flag=1;dfs(v,dp+1,u); 
	}
}
void dfs2(int u){
	vis[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(vis[v])continue;
		dfs2(v);ji[u]+=ji[v];ou[u]+=ou[v];
		edge[(i%2)?((i+1)/2):(i/2)].ji=ji[v];edge[(i%2)?((i+1)/2):(i/2)].ou=ou[v];
	}
}
int ans[2000003],cnt;
signed main(){
//	freopen("fairy.in","r",stdin);
//	freopen("fairy.out","w",stdout);
	n=in;m=in;
	for(int i=1;i<=m;i++){
		int a,b;a=in;b=in;add(a,b);add(b,a);edge[i].u=a,edge[i].v=b;
	}
	for(int i=1;i<=n;i++){
		if(!vis[i])dfs(i,1,0);
	}tot=0;
//	for(int i=1;i<=n;i++)cout<<dep[i]<<" ";cout<<endl;
//	for(int i=1;i<=m;i++)cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].flag<<endl; 
	for(int i=1;i<=m;i++){
		if(edge[i].flag)continue;//cout<<"#";
		int u=edge[i].u,v=edge[i].v;//cout<<" "<<u<<" "<<v<<" "<<abs(dep[u]-dep[v])<<endl;
		if(dep[u]>dep[v])swap(u,v);//u当然是在上面的♂ 
		if((dep[v]-dep[u])&1)ou[u]--,ou[v]++;
		else ji[u]--,ji[v]++,tot++,edge[i].ji++; 
	}//cout<<"## "<<tot<<" ## "<<endl;
	if(tot==0){
		printf("%d\n",m);
		for(int i=1;i<=m;i++){
			printf("%d ",i);
		}return 0;
	}
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)if(!vis[i])dfs2(i);
	for(int i=1;i<=m;i++){
		//cout<<edge[i].ou<<" "<<edge[i].ji<<endl;
			if(!edge[i].ou&&edge[i].ji==tot)ans[++cnt]=i;
	}
	cout<<cnt<<endl;
	for(int i=1;i<=cnt;i++)printf("%d ",ans[i]);
	return 0;
}

ps:为什么我会做没人A的题而人人A的题我永远爆零,,谁来解释一下qwq

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值