【BZOJ4424】Fairy(树上差分)

点此看题面

大致题意: 一张图有\(n\)个点,\(m\)条边,你可以从中删去一条边。问当你删的是哪些边时,得到的图是二分图。

前言

\(Jan\ 28th\)刷题计划(5/6),算法标签:图论、dfs树、二分图。

又一次败给了自己的智障:双向边忘记建两次了......

奇环

二分图一个重要性质:没有奇环。

首先,我们随便从这张图中找出一棵生成树来(由于图不一定连通,所以可能会得到森林)。

然后枚举每一条非树边,将其放在树上看一看会不会生成奇环(这就相当于是判断两点的树上距离加\(1\)的奇偶性)。

易统计出会生成的奇环个数,接下来我们对此分类讨论:

  • 奇环个数为\(0\):这张图原本就是二分图,那么随便删一条边之后它都是二分图。
  • 奇环个数为\(1\):可以删去构成奇环的那条非树边,或者某些树边。如果要删去一条树边,则需要满足它没有和其他非树边构成偶环,不然构成奇环的非树边和构成偶环的非树边之间就会构成奇环(可以自己画个图理解一下)。
  • 奇环个数大于\(1\):求出所有奇环的公共部分,然后这一公共部分上没有和其他非树边构成偶环的边就是可以被删除的边,原因同上。

总结一下,奇环个数为\(0\)的情况是很好处理的,而奇环个数为\(1\)的情况就是在奇环个数大于\(1\)的情况的基础上加了一条构成奇环的非树边。

所以,我们重点要考虑的是如何处理奇环个数大于\(1\)的情况。

树上差分

考虑我们要求出的边,满足是所有奇环的公共部分,且不属于任意偶环。

于是我们可以用树上差分来实现链边权的加减。

如果只考虑第一个条件,对于每个奇环,我们将构成它的树边边权加\(1\),则最后边权等于奇环个数的边就是可以被删除的边。

而要处理第二个条件其实也很容易,对于每个偶环,我们将构成它的树边边权减\(1\),这样就可以保证这些边的边权不可能为奇环个数了。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define LN 20
#define swap(x,y) (x^=y^=x^=y)
#define pb push_back
using namespace std;
int n,m,cnt;vector<int> ans;struct line {int x,y,p;}s[N+5];
namespace UnionFindSet//并查集判连通
{
	int fa[N+5];I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}
	I void Union(CI x,CI y) {fa[getfa(x)]=getfa(y);}
	I bool Identify(CI x,CI y) {return getfa(x)==getfa(y);}
}
namespace Tree
{
	int tot,ee,lnk[N+5],dep[N+5],fa[N+5][LN+5],v[N+5],p[N+5];struct edge {int to,nxt,pos;}e[N<<1];
	I void Add(CI x,CI y,CI z) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].pos=z;}
	I void Init(CI x,CI lst=0)//初始化
	{
		RI i;for(i=1;i<=LN;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
		for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
			(dep[e[i].to]=dep[fa[e[i].to][0]=x]+1,Init(e[i].to,x),0);
	}
	I int LCA(RI x,RI y)//倍增LCA
	{
		RI i;dep[x]<dep[y]&&swap(x,y);
		for(i=0;dep[x]^dep[y];++i) ((dep[x]^dep[y])>>i)&1&&(x=fa[x][i]);if(x==y) return x;
		for(i=LN;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0];
	}
	I bool Check(CI x,CI y)//判断是否为奇环
	{
		#define Upt(t) (x==z?(v[y]+=t,v[x]-=t):(y==z?(v[x]+=t,v[y]-=t):(v[x]+=t,v[y]+=t,v[z]-=2*t)))//修改构成环的边的权值
		RI z=LCA(x,y);return (dep[x]+dep[y]-(dep[z]<<1)+1)&1?(++tot,Upt(1),true):(Upt(-1),false);
	}
	I void Calc(vector<int>& V,CI x,CI lst=0,CI pre=0)//将差分标记推掉
	{
		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(Calc(V,e[i].to,x,e[i].pos),v[x]+=v[e[i].to]);
		pre&&v[x]==tot&&(V.pb(pre),0);//如果边权等于奇环个数,就可以删去
	}
	I void GetDep() {for(RI i=1;i<=n;++i) !dep[i]&&(Init(i),0);}//以深度为0判断根节点
	I void GetAns(vector<int>& V) {for(RI i=1;i<=n;++i) !dep[i]&&(Calc(V,i),0);}//以深度为0判断根节点
}
int main()
{
	RI i,x,y,t;using namespace UnionFindSet;
	for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),
		Identify(x,y)?(s[++cnt]=(line){x,y,i},0):(Tree::Add(x,y,i),Tree::Add(y,x,i),Union(x,y),0);
	for(Tree::GetDep(),i=1;i<=cnt;++i) Tree::Check(s[i].x,s[i].y)&&(t=s[i].p);//枚举非树边
	if(!Tree::tot) {for(printf("%d\n",m),i=1;i<=m;++i) printf("%d ",i);return 0;}//没有奇环
	Tree::GetAns(ans),Tree::tot==1&&(ans.pb(t),0);//求出奇环个数大于1情况的答案,再特殊处理奇环个数等于1的情况
	printf("%d\n",ans.size()),sort(ans.begin(),ans.end());vector<int>::iterator it;
	for(it=ans.begin();it!=ans.end();++it) printf("%d ",*it);return 0;//输出答案
}
posted @ 2020-01-28 20:07  TheLostWeak  阅读(...)  评论(...编辑  收藏