CF19E Fairy

纪念一下我逝去的语文(110的蒟蒻膜拜AK的大佬)

一、题目

传送门
题意:
对于一个图进行二染色,求删去哪些边可以是得染色成功(一定有解),输出可能的边数及可能边的编号(输入顺序)。
数据范围:
1 ≤ n ≤ 1 0 4 , 0 ≤ m ≤ 1 0 4 1\leq n\leq 10^{4},0\leq m\leq 10^{4} 1n104,0m104

二、解法

0x01 前置芝士
先给出一个结论:一个图能染色成功的充要条件是该图中不存在奇环(边数为奇数的环)。
怎么理解这个结论呢?我们先建一颗搜索树,我们把边慢慢加 ,新加入的边会让搜索树构成环,如果此环为奇环,那么树上路径的边数为偶数,两点的颜色一样,故不能有奇环,详细证明需要讨论链式和弯折式的情况。
0x02 分析
我们先建一颗生成树,把没有出现过得边加入。
首先如果没有奇环,直接输出所有边即可。
如果有一个奇环,我们删去环上任意一条边即可。
我们考虑多个奇环的情况,由于要同时破环所有奇环,我们考虑把每次加入边所对应的树上路径全部加1,最后统计所有权值等于奇环数的树边,树上差分可以做到 O ( n ) O(n) O(n)
当这里还存在一个难以解决的问题,形成偶环的边可能回和形成奇环的边组合成奇环,而我们并没有统计到,我们尝试对组合的情况进行讨论。
设奇环树上边数为J,偶环树上边数为O。

第一种情况(两边路径均有交集),我们发现第一条红线组成了一个奇环,第二条红线组成了一个偶环,
如果删去树上边的环,剩下的两个红边组成的环的数量为 O − J + 2 O-J+2 OJ+2,易得改换为奇环,所以不能删去树上边。

第二种情况,若删去树边,则能组成的新环边数为 O − s 1 + s 2 + 2 O-s_1+s_2+2 Os1+s2+2 s 1 , s 2 s_1,s_2 s1,s2为奇环树上的两边边数,所以 s 1 , s 2 s_1,s_2 s1,s2同奇同偶,易得该新环为奇环,故该树边不能选。
这两种情况是最基本的,可以衍生出其他情况,我们可以发现偶环的树上路径都是不能选的,这个也可以通过树上差分实现。
0x03 代码

#include <cstdio>
#include <algorithm>
using namespace std; 
const int MAXN = 10005;
int read()
{
    int num=0,flag=1;
    char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,k,cnt,tot,thm,flag,num[MAXN],c[MAXN],f[MAXN],p[MAXN],dep[MAXN],ans[MAXN],fa[MAXN][20];
bool vis[MAXN];
struct node
{
	int u,v,id;
}a[MAXN],b[MAXN];
struct edge
{
	int v,next,id;
}e[MAXN*2];
int findSet(int x)
{
	if(p[x]^x) p[x]=findSet(p[x]);
	return p[x];
}
void dfs(int u,int par)
{
	fa[u][0]=par;
	dep[u]=dep[par]+1;
	for(int i=1;i<20;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=f[u];i;i=e[i].next)
		if(e[i].v^par)
		{
			num[e[i].v]=e[i].id;
			dfs(e[i].v,u);
		}
}
int Lca(int u,int v)
{
	for(int i=19;i>=0;i--)
	{
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
		if(dep[fa[v][i]]>=dep[u])
			v=fa[v][i];
	}
	if(u==v) return u;
	for(int i=19;i>=0;i--)
		if(fa[u][i]^fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void make(int u,int par)
{
	vis[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==par) continue;
		make(v,u);
		c[u]+=c[v];
	}
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		p[i]=i;
	for(int i=1;i<=m;i++)
		a[i]=node{read(),read(),i};
	for(int i=1;i<=m;i++)
	{
		int x=findSet(a[i].u),y=findSet(a[i].v);
		if(x^y)
		{
			p[x]=y;
			e[++tot]=edge{a[i].u,f[a[i].v],a[i].id},f[a[i].v]=tot;
			e[++tot]=edge{a[i].v,f[a[i].u],a[i].id},f[a[i].u]=tot;
		}
		else
			b[++k]=a[i];
	}
	for(int i=1;i<=n;i++)
		if(!dep[i]) dfs(i,0);
	for(int i=1;i<=k;i++)
	{
		int u=b[i].u,v=b[i].v,lca=Lca(u,v);
		int len=dep[u]+dep[v]-dep[lca]*2,delta=1;
		if(len&1) delta=-1;
		else flag++,thm=b[i].id;
		c[u]+=delta,c[v]+=delta,c[lca]-=2*delta;
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) make(i,0);
	if(!flag)
		for(int i=1;i<=m;i++)
			ans[++cnt]=i;
	else 
		for(int i=1;i<=n;i++)
			if(c[i]==flag)
				ans[++cnt]=num[i];
	if(flag==1) ans[++cnt]=thm;
	sort(ans+1,ans+1+cnt);
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
		printf("%d ",ans[i]); 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值