删掉一条边,使得原图成为一个二分图。
二分图充要条件:无奇环。
一个很美好的二分图:树
二分图:删了一条边还是二分图
所以对于原图,随手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