bzoj2503 相框(思维题)(并查集)

题目

bzoj2503 相框

特性

如果原图是一个连通图,并且为欧拉回路,那么把所有点熔开即可。
其实上面这句话是废话
一个多条边(>2)连接的点一定要熔。
一个奇数条边相连的点一定是熔完后要接的。

题解

乱搞+并查集
其实上面的几条边相连都可以用“度”来表达。
所以得到一个乱搞算法:
1、每有一个多度点,ans++;
2、每有一个奇度点,cnt++。
对于那些自由边,另开一个点给它们。
还有一个问题,怎么把两个环合成一个呢?
所以先要维护一下连通性,在无向图中,并查集是首选。
对于一个连通块,因为要跟其它块合并,所以它必须断开一个口来,那么会多两个奇度点,cnt+=2。断开口的点需要熔,代价为1,ans++。特别的,如果一个点本身就需要熔(度数大于2),那么它可以考虑熔出两条一端自由的边出来,此时就可以省一次的熔。又特别的,如果一个连通块中本身就有奇度点,那么只要保留它就可以了,这下连合并熔的代价也可以省掉(其实是已经算过一次了)。
有人会问,会不会存在只有一个奇度点的图,这样的话我们还要花费一些代价熔开其它点来制造奇度点?答案是否定的,直接给出定律:一个图的奇度点必为偶数个,可以用所有点度数之和为偶数来证明。
所以要维护一下每个连通块中是否存在多度点和奇度点。

代码

#include
#include
#include
using namespace std;
const int MAXN=1010,MAXM=50010;
 
int n,m;
int deg[MAXM*3];
bool bk[MAXM*3],dev[MAXM*3];
 
int fa[MAXM*3];
int findfa(int x){return x==fa[x]?fa[x]:fa[x]=findfa(fa[x]);}
 
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(x==0) fa[++n]=x=n;if(y==0) fa[++n]=y=n;
        deg[x]++;deg[y]++;
        fa[findfa(x)]=findfa(y);
    }
    int ans=0,cnt=0,num=0;
    for(int i=1;i<=n;i++)
    {
        if(deg[i]==0) continue;//debug 度数为0不考虑 
        if(findfa(i)==i) num++;
        if(deg[i]&1) bk[fa[i]]=true,cnt++;
        if(deg[i]>2) dev[fa[i]]=true,ans++;
    }
    if(num!=1)
        for(int i=1;i<=n;i++) if(deg[i]!=0 && fa[i]==i && bk[i]==false)
        {
            cnt+=2;
            if(!dev[i]) ans++;
        }
    ans+=cnt/2;
    printf("%d\n",ans);
    return 0;
}

 

你可能感兴趣的:(并查集,刷题之路,思维题,欧拉路)