题面
Description
给定 n n n个点, m m m条边的无向图(无自环),可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。
Input
第 1 1 1行包含两个整数 n n n, m m m,分别表示点数和边数。
第 2 ∼ m + 1 2\sim m+1 2∼m+1行每行两个数 x x x, y y y,表示有一条边连接点 x x x, y y y。
Output
第一行两个整数,表示能删除的边的个数。
接下来一行按照从小到大的顺序输出能删除的边的编号。
Sample Input
4 4
1 2
1 3
2 4
3 4
Sample Output
4
1 2 3 4
HINT
10 % 10\% 10%的数据, n , m < = 10 n,m<=10 n,m<=10
40 % 40\% 40%的数据, n , m < = 1000 n,m<=1000 n,m<=1000
70 % 70\% 70%的数据, n , m < = 100000 n,m<=100000 n,m<=100000
100 % 100\% 100%的数据, n , m < = 2000000 n,m<=2000000 n,m<=2000000
题解
考虑如何判断一个图是否为二分图:判断图中有无奇环,这可以用 d e e p deep deep或 c o l o r color color(染色法)维护。
那么假设图中有 k k k个奇环,要删掉一条边使得新图是二分图,就要把这 k k k个奇环破坏或去掉,也就是把这 k k k个奇环的共边中删掉任意一条。
也就是说,假设原图的某条边在所有的奇环上,就可以被删掉。
但还是有个问题,假设有一条边是两个奇环的共边,那么删掉这条边后两个奇环又会组成一个环,那这个环是奇是偶呢?
答案是偶,因为这个环的总点数等于原来两个奇环的点数之和减 2 2 2,为偶数。
那我们就给每条边设一个 n u m num num,如果有一个奇环经过这条边,就 n u m [ i ] + + num[i]++ num[i]++,最后看一下这条边的 n u m num num是否等于总奇环数就好了。
但这样找边会 W A \color{red}{\mathrm{WA}} WA,为什么?
考虑下图的一种情况:
其中,红色边连着的是一个奇环,蓝色边连着的是一个偶环。
如果按照刚刚的理论,应该这些边都是可以删掉的: ( 1 , 2 ) (1,2) (1,2)、 ( 1 , 3 ) (1,3) (1,3)、 ( 2 , 3 ) (2,3) (2,3)。
可是我们发现,当我们删掉边 ( 2 , 3 ) (2,3) (2,3)时,新图还是一个奇环( 1 ⟶ 2 ⟶ 4 ⟶ 5 ⟶ 3 ⟶ 1 1\longrightarrow2\longrightarrow4\longrightarrow5\longrightarrow3\longrightarrow1 1⟶2⟶4⟶5⟶3⟶1)。
问题就在于,当一条边既在奇环上(设奇环点数为 a a a,则 a a a为奇数),又在偶环上时(设偶环点数为 b b b,则 b b b为偶数),删掉这条边后奇环和偶环会组成一个环,且点数为 a + b − 2 a+b-2 a+b−2,是个奇环。
所以当一条边既在奇环上又在偶环上时,我们不能选它。
所以我们还要给每条边设一个 f l a g flag flag判断它在不在偶环上。
记得特判没有奇环的情况,这时任意删掉哪条边都可以。
最后代码如下:
#include<bits/stdc++.h>
#define N 4000010
using namespace std;
int n,m,ans[N];
int cnt,tot,idx,top,head[N],nxt[N],to[N],id[N];
int col[N],num[N],st[N],dfn[N];
bool flag[N],vis[N],use[N];
void adde(int u,int v,int ii)
{
to[++cnt]=v;
id[cnt]=ii;//id即这条边对应的是输入中的第几条
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs(int u,int fa)
{
dfn[u]=++idx;
for(int i=head[u];i;i=nxt[i])
{
if(dfn[to[i]]>dfn[u]||to[i]==fa)
continue;
if(col[to[i]]!=-1)//如果已经访问过
{
if(col[to[i]]==col[u])//颜色相等即奇环
{
tot++;
num[id[i]]++;
for(int j=top;j&&to[st[j]]!=to[i];j--)//循环枚举环上的每一个点
num[id[st[j]]]++;//记录num
}
else
{//颜色不等即偶环
for(int j=top;j&&to[st[j]]!=to[i];j--)
flag[id[st[j]]]=true;//标记flag
}
continue;
}
col[to[i]]=col[u]^1;//染色
st[++top]=i;//丢进stack
dfs(to[i],u);
st[top--]=0;//记得弹出来
}
}
void check(int u)
{
vis[u]=true;
for(int i=head[u];i;i=nxt[i])
{
if(!use[id[i]]&&((!flag[id[i]]&&num[id[i]]==tot)||!tot))//如果这条边还没有记录在答案里(去重),且没有奇环,或者这条边在所有奇环上且不在偶环上
{
use[id[i]]=true;
ans[++ans[0]]=id[i];
}
if(!vis[to[i]])
check(to[i]);
}
}
int main()
{
memset(col,-1,sizeof(col));//每个点的颜色(初始化)
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
adde(u,v,i),adde(v,u,i);//建边
}
for(int i=1;i<=n;i++)
{
if(col[i]==-1)
{
tot=0;
col[i]=0;
dfs(i,-1);
check(i);
}
}
sort(ans+1,ans+ans[0]+1);
printf("%d\n",ans[0]);
for(int i=1;i<=ans[0];i++)
printf("%d ",ans[i]);
return 0;
}