相框

https://loj.ac/problem/10111

题目描述

  给出一张图,可以有两种操作:\(①\)把一个点拆成若干点,连在这个点上的边可以任意连到拆成的点上。\(②\)将两条边自由端熔融成一个点。求最少的操作数使得这张图变成一个简单环。

思路

  还是一样,我们从最简单开始,如果图是一个连通图,那么我们考虑最后的简单环每个点的度均为\(2\),所以我们计奇数点的个数为\(cnt1\),度大于\(2\)的点的个数为\(cnt2\),那么对于\(cnt2\)个点,我们都需要进行至少一次的熔断,接下来思考能如何进行最少的熔融次数使图变成一个简单环,由于奇数的存在,所以熔断之后必定仍然存在奇数点,因此以最少的次数消去奇数点至少要\(\frac{cnt}{2}\)次(奇数点个数必定为偶数),答案即为\(cnt2+\frac{cnt1}{2}\)

  再复杂化,如果是由多个连通块构成,那么我们先要考虑把每个连通块都操作成一条链,再通过熔融操作连接起来,对于每个一个连通块,我们进行分类讨论:

  \(①\)如果存在奇数点,那么就不用管,显然可以通过\(cnt2+\frac{cnt1-2}{2}\)变为一条链,再加上分摊到每个连通块的一个熔融连接操作,不改变操作次数。

  \(②\)如果不存在奇数点,就需要造出两个度为\(1\)的点,\(cnt1\)要加\(2\);

   (一)如果存在度大于\(2\)的点,可以在熔断的时候分割成两个度为\(1\)的点

   (二)如果不存在度大于\(2\)的点,就需要额外一次操作造出这样一个节点来完成分割,\(cnt2\)\(1\)

  剩下就是一些细节问题,由于点是可以乱搞的,重要的是边,所以孤立点不用考虑。判连通性时用并查集即可,注意初始赋值不是\(n\),因为有\(0\)号节点存在,最多点数为\(m*2\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2100,M=55000<<1;

struct Edge
{
    int x,y;
}e[M];

int fa[M];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void f_union(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)fa[fx]=fy;
}

int read()
{
    int res=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+ch-'0';ch=getchar();}
    return res*w;
}

int deg[M];
bool flag1[M],flag2[M];
int main() 
{
    int n,m;
    n=read();m=read();
    for(int i=1;i<=m*2;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        if(!x)x=++n;
        if(!y)y=++n;
        e[i].x=x;e[i].y=y;
        deg[x]++;deg[y]++;
        f_union(x,y);
    }
    int sum=0; 
    int cnt1=0,cnt2=0;
    for(int i=1;i<=n;i++)
    {
        if(deg[i]==0)continue ;
        if(find(i)==i)sum++;
        if(deg[i]&1)
        {
            cnt1++;
            flag1[find(i)]=1;
        }
        if(deg[i]>2)
        {
            cnt2++;
            flag2[find(i)]=1;
        }
    }
    if(sum>1)
        for(int i=1;i<=n;i++)
            if(deg[i]&&find(i)==i&&!flag1[i])
            {
                cnt1+=2;
                if(!flag2[i])++cnt2;
            }
    printf("%d",cnt2+cnt1/2);
    return 0;
}
posted @ 2019-10-28 15:55  fbz  阅读(131)  评论(0编辑  收藏  举报