zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    涂鸦
    触发事件续
    触摸事件基本介绍
    背景平铺
    屏幕截图
    图片的裁剪
    图片水印
    UIKit绘图方法
    Java范型学习笔记
    《Head first设计模式》学习笔记
  • 原文地址:https://www.cnblogs.com/clno1/p/10844666.html
Copyright © 2011-2022 走看看