其他分享
首页 > 其他分享> > (好题) Codeforces 19E&BZOJ 4424 Fairy

(好题) Codeforces 19E&BZOJ 4424 Fairy

作者:互联网

 日常自闭(菜鸡qaq)。不过开心的是看了题解之后1A了。感觉这道题非常好,必须记录一下,一方面理清下思路,一方面感觉自己还没有完全领会到这道题的精髓先记下来以后回想。

题意:给定 n 个点,m 条边的无向图,可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。

看到二分图,我们肯定会想到奇环。要删除一条边使得剩下的图变成二分图,那么必定剩下的图也不能存在奇环。所以要删除的边都必须经过所有的奇环。这比较好想,但是还有一个难想一点的条件,要删除的边也不能经过任何的偶环,这是因为如果这条边经过了偶环,哪怕删除了这条边剩下的边也会形成新的奇环。为什么呢?画一下图就发现,假定奇环长度为A,偶环长度为B,他们共享的边长度为C,那么他们能形成新的长度为A+B-2*C长度的环,显然这个数是奇数。

综上,能删除的边满足上诉两个条件。我们得到这样一个算法:对所有奇环的边+1,对所有偶环的边-1,最后边值等于奇环个数的就是满足条件的边。 那么我们可以考虑用差分完成这个事情,用d[x]代表dfs树进入x点的边的差分值。染色的过程差分,染完色之后计算便值即可。

当然这道题还没完,以上的过程我们只考虑了dfs树边的情况,但是返祖边(非树边)呢?我们继续思考:经过上面的思考我们还是只考虑奇环上的返祖边,仔细画图观察:在dfs搜索树上,一个奇环返祖边只会属于一个奇环。根据我们上边的结论:边必须要经过所有奇环,可以得到:奇环返祖边能满足条件当且仅当图上只有一个奇环。

接下来还得注意一个小细节:自环。自环我们可以当中奇环,但是这种奇环没有返祖边,所以我们得特殊处理。

呼呼,到这里我们终于能AC了。

细节详见代码以注释:

#pragma comment(linker,"/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,odd,rec,num,col[N],vis[N],tag[N],dfn[N];
vector<int> ans;

int cnt=1,head[N],nxt[N<<1],to[N<<1],id[N<<1];
void add_edge(int x,int y,int z) {
    nxt[++cnt]=head[x]; to[cnt]=y; id[cnt]=z; head[x]=cnt;
}

void dfs1(int x,int t,int in) {
    col[x]=t; dfn[x]=++num;
    for (int i=head[x];i;i=nxt[i]) {
        if (i==(in^1)) continue;  //这样能处理重边的情况 
        int y=to[i];
        if (!col[y]) dfs1(y,3-t,i);
        else {
            if (dfn[x]<dfn[y]) continue;  //这是一个细节,dfn[x]比dfn[y]大的才是返祖边 
            if (col[x]==col[y]) {  //奇环 
                odd++; rec=i;
                tag[x]++; tag[y]--;
            } else {  //偶环 
                tag[x]--; tag[y]++;
            }
        }
    }
}

void dfs2(int x,int in) {
    vis[x]=1;
    for (int i=head[x];i;i=nxt[i]) {
        if (i==(in^1)) continue;
        int y=to[i];
        if (!vis[y]) {
            dfs2(y,i);
            tag[x]+=tag[y];  //累计子树差分值得到 x入边值 
        }
    }
    if (tag[x]==odd) ans.push_back(in);  //满足条件的边 
}

int main()
{
    cin>>n>>m;
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%d%d",&x,&y);
        if (x==y) {  //特别处理自环的情况 
            odd++; add_edge(0,0,i); rec=cnt; continue;
        }
        add_edge(x,y,i); add_edge(y,x,i);
    }
    
    for (int i=1;i<=n;i++)
        if (!col[i]) dfs1(i,1,0);  //染色 
    for (int i=1;i<=n;i++)
        if (!vis[i]) dfs2(i,0);  //计算边值 
    
    if (odd==0) {
        cout<<m<<endl;
        for (int i=1;i<=m;i++) printf("%d ",i);
    } else {
        if (odd==1) ans.push_back(rec);  //奇环返祖边 
        cout<<ans.size()<<endl;
        sort(ans.begin(),ans.end());
        for (int i=0;i<ans.size();i++) printf("%d ",id[ans[i]]);
    }
    return 0;
}

 

标签:删除,返祖,好题,4424,Codeforces,int,奇环,长度,偶环
来源: https://www.cnblogs.com/clno1/p/10844666.html