【XSY2508】【BZOJ4424】Fairy(二分图)

题面

Description

给定\(n\)个点,\(m\)条边的无向图(无自环),可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。

Input

\(1\)行包含两个整数\(n\),\(m\),分别表示点数和边数。

\(2\sim m+1\)行每行两个数\(x\),\(y\),表示有一条边连接点\(x\),\(y\)

Output

第一行两个整数,表示能删除的边的个数。

接下来一行按照从小到大的顺序输出能删除的边的编号。

Sample Input

4 4
1 2
1 3
2 4
3 4

Sample Output

4
1 2 3 4

HINT

\(10\%\)的数据,\(n,m<=10\)

\(40\%\)的数据,\(n,m<=1000\)

\(70\%\)的数据,\(n,m<=100000\)

\(100\%\)的数据,\(n,m<=2000000\)

题解

考虑如何判断一个图是否为二分图:判断图中有无奇环,这可以用\(deep\)\(color\)(染色法)维护。

那么假设图中有\(k\)个奇环,要删掉一条边使得新图是二分图,就要把这\(k\)个奇环破坏或去掉,也就是把这\(k\)个奇环的共边中删掉任意一条。

也就是说,假设原图的某条边在所有的奇环上,就可以被删掉。

但还是有个问题,假设有一条边是两个奇环的共边,那么删掉这条边后两个奇环又会组成一个环,那这个环是奇是偶呢?

答案是偶,因为这个环的总点数等于原来两个奇环的点数之和减\(2\),为偶数。

那我们就给每条边设一个\(num\),如果有一个奇环经过这条边,就\(num[i]++\),最后看一下这条边的\(num\)是否等于总奇环数就好了。

但这样找边会\(\color{red}{\mathrm{WA}}\),为什么?

考虑下图的一种情况:

其中,红色边连着的是一个奇环,蓝色边连着的是一个偶环。

如果按照刚刚的理论,应该这些边都是可以删掉的:\((1,2)\)\((1,3)\)\((2,3)\)

可是我们发现,当我们删掉边\((2,3)\)时,新图还是一个奇环(\(1\longrightarrow2\longrightarrow4\longrightarrow5\longrightarrow3\longrightarrow1\))。

问题就在于,当一条边既在奇环上(设奇环点数为\(a\),则\(a\)为奇数),又在偶环上时(设偶环点数为\(b\),则\(b\)为偶数),删掉这条边后奇环和偶环会组成一个环,且点数为\(a+b-2\),是个奇环。

所以当一条边既在奇环上又在偶环上时,我们不能选它。

所以我们还要给每条边设一个\(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;
}

猜你喜欢

转载自www.cnblogs.com/ez-lcw/p/11491326.html
今日推荐