Codeforces 557D 题解

题面

给定一个简单无向图,求至少要加多少条边才能使图中包含至少一个简单奇环,求加边的方案数。

$ n,m \leq 10^5 $

Translated by @高飞@Luogu

输入格式

第一行 $n,m$,表示点、边的个数。

接下来 $m$ 行,每行两个整数 $a,b$,表示 $a$ 与 $b$ 之间有一条无向边。

输出格式

一行两个整数,表示最少加边的个数、加边的方案数。

题解

首先分析样例。

Input #1

4 4
1 2
1 3
4 2
4 3

Output #1

1 2

分析 #1

只需要添加紫色边或蓝色边即可构成奇环。

Input #2

3 3
1 2
2 3
3 1

Output #2

0 1

分析 #2

已经成奇环。

Input #3

3 0

Output #3

3 1

分析 #3

自己连个大小为 $3$ 的环出来即可,可以发现任意图中我们都可以用这个方法造出一个环,也就是需要增加的边数一定不会超过 $3$。

那么,答案只可能是 $0,1,2,3$。

我们来逐个解决。

加边数 = 0

直接判断图中是否已有奇环即可,我们用二分图染色的方法来求。

对于每一个联通块进行搜索,交替的进行黑白染色,如果发现已经染过色了,且将要染的色和原色冲突,即一定有奇环,输出 0 1 即可。

加边数 = 1

可以发现,如果一个联通块中有一条长度 $\geq 3$ 且为奇数的路径,即可直接连首尾,形成一个环。

那么如何求方案数呢?还是用染色。任意选两个相同颜色的连一下边都是一种合法方案。

统计联通块 $i$ 中染出的黑、白个数 $cnt[i][0], cnt[i][1]$

则方案数可表示为 $\sum_{i=1}^{totblk}({cnt[i][0]\choose 2}+{cnt[i][1]\choose 2})$

加边数 = 2

这个情况下,每个联通块的大小都只为 $1$ 或 $2$,且必定有一个为 $2$ 的联通块。

只需要从大小为 $2$ 的联通块连两条边到任意其他点上即可。

如图,考虑 ${4,5}$ 这个联通块,向任意其他点连两条边都可以组成一个奇环。

加边数 = 3

这种情况下,显然 $m=0$。

方案数即为 $n\choose 3$

实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge{
    int to,nxt;
}ed[100010*2];int edcnt=0;
int hd[100010];
inline void adde(int u,int v){
    ed[++edcnt].to=v;
    ed[edcnt].nxt=hd[u];
    hd[u]=edcnt;
}
int blk[100010],vis[100010],col[100010],cnt[100010][2];
int n,m;
int nice;
void dfs(int blkid,int u,int fa,int dep){
    blk[u]=blkid;
    if(vis[u]){
        if(col[u]!=!col[fa])
            nice++; //奇环标记
        return;
    }
    vis[u]=1;
    col[u]=!col[fa];
    cnt[blkid][col[u]]++;
    for(int id=hd[u];id;id=ed[id].nxt){
        int v=ed[id].to;
        if(v==fa)continue;
        dfs(blkid,v,u,dep+1);
    }
}
int totblk=0; //联通块数目
ll ans=0;
inline ll C3(ll x){
    return x*(x-1)*(x-2)/2/3;
}
inline ll C2(ll x){
    return x*(x-1)/2;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;++i){
        scanf("%d%d",&u,&v);
        adde(u,v);
        adde(v,u);
    }

    // 加边数=3
    if(m==0){
        printf("3 %lld",C3(n));
        return 0;
    }

    int oneok=0; // 标记花费为1是否可能
    for(int i=1;i<=n;++i){
        if(blk[i])continue;
        dfs(++totblk,i,i,0);
        if(cnt[totblk][0]>=2 || cnt[totblk][1]>=2) oneok=1;
        if(nice){ // 奇环直接输出
            puts("0 1");
            return 0;
        }
    }
    ll ans=0;

    // 加边数=1
    if(oneok){
        for(int i=1;i<=totblk;++i){
            if(cnt[i][0]>=2) ans+=C2(cnt[i][0]);
            if(cnt[i][1]>=2) ans+=C2(cnt[i][1]);
        }
        printf("1 %lld",ans);
        return 0;
    }

    // 加边数=2
    ll tot=0;
    for(int i=1;i<=totblk;++i){
        cnt[i][0]+=cnt[i][1]; // 无需考虑颜色,为方便加到一起
        tot+=cnt[i][0];
    }
    for(int i=1;i<=totblk;++i){
        if(cnt[i][0]==2){
            ans+=tot-cnt[i][0];
        }
    }
    printf("2 %lld",ans);
}