Brief Intro:
给定一个无向图,询问删去哪条边能使得剩下的图为一个二分图,输出所有结果
Algorithm:
关于二分图的重要性质:一个图为二分图的充要条件为其中没有奇环
1、如果没有奇环,那么删去任何一条边都不会使其变为奇环
2、如果存在一个或多个奇环,删去的边必为所有奇环的一条公共边
但这样仍然无法保证残余图是一个二分图:因为有偶环的存在
为了寻找奇环或者偶环,我们一般采取dfs树的方式
我们可以证明(奇偶性判断即可)当一个环上有两个偶返祖边或两个奇返祖边时,这个环必为偶环
而当一个环上有一个奇返祖边以及一个偶返祖边时,这个环必为奇环
如果一条边有一个奇环以及一个偶环通过,那么删去这条边后一定会出现一个环中包含一个奇返祖边和一个偶返祖边
从而出现奇环,不成立
题目被转化为:求出既是所有奇环公共边又不在任何一个偶环中的边
dfs树维护每条边经过的奇/偶环个数即可
Code:
#include <bits/stdc++.h> using namespace std; typedef pair<int,int> P; typedef long long ll; #define F first #define S second const int MAXN=1e4+10; int n,m,odd[MAXN],even[MAXN],vis[MAXN],match[MAXN],col[MAXN],st,cnt; vector<P> G[MAXN]; vector<int> res; void dfs(int cur,int anc) { vis[cur]=1;col[cur]=col[anc]^1; for(int i=0;i<G[cur].size();i++) { P t=G[cur][i]; if(t.F==anc) continue; if(!vis[t.F]) //将点和其父边配对 match[t.F]=t.S,dfs(t.F,cur),odd[cur]+=odd[t.F],even[cur]+=even[t.F]; else if(vis[t.F]==1) col[cur]==col[t.F]?odd[cur]++,st=t.S,cnt++:even[cur]++; else col[cur]==col[t.F]?odd[cur]--:even[cur]--; //如果到了一个环的起始点,那么该点的父边不在环中 } vis[cur]=-1; //不同的标记 } int main() { cin >> n >> m; for(int i=1;i<=m;i++) { int x,y;cin >> x >> y; G[x].push_back(P(y,i));G[y].push_back(P(x,i)); } for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0); if(cnt==0) for(int i=1;i<=m;i++) res.push_back(i); else { if(cnt==1) res.push_back(st); //如果只有一个奇环,起始边也符合条件 for(int i=1;i<=n;i++) if(odd[i]==cnt && !even[i]) res.push_back(match[i]); sort(res.begin(),res.end()); } cout << res.size() << endl; for(int i=0;i<res.size();i++) cout << res[i] << " "; return 0; }
Review:
1、二分图的充要条件:不含奇环
同时不用考虑原图中的所有环,而只要考虑dfs树构造出的环即可
2、可采用dfs树的方式计算图中的奇、偶环
注意对一个环起始点的特殊处理
(UPD:实际上这是一个$dfs$树上差分的过程)
3、由于dfs树的无后效性,如果一个大环中包含两条返祖边,去掉两小环的公共路径*2,不改变两小环总和的奇偶性,从而能推断出大环的奇偶性
利用dfs树中的迭代性、公共路径推出有用的结论
4:注意很多时候只要考虑$dfs$返祖边的环即可!!!!