Codeforces 788B Weird journey (欧拉回路 组合数计数)

Codeforces 788B Weird journey

description

给定一个n个点m条边的无向图
可以有自环
一条合法路径的要求是经过全部的m条边,其中2条边都经过一次,其余m-2条边经过两次
两条路径不同当且仅当其中有一条边在两种路径中经过次数不同
求路径数

input
第一行两个整数n,m
接下来m行每行两个整数u,v,表示u到v有一条边

output
一个整数表示答案

思路:
<摘自BerryKanry>
不同路径不同仅在某一条边经过次数不同,而路径要求每条边经过一次或者两次,等价于只要两条路径选择的只经过一次的两条边不同,换句话说就是任意两个不相同的边决定了两条不相同的路径。

考虑两条边作为路径上只经过一次的两条边,可以使得路径满足条件的条件,由于除了这两条边以外的边都要走两次,就是说走了还要走回来,先考虑所有的边走两次,相当于就是用一条线把整个图绕一圈,要想使得其中某条边只绕一次,就相当于绕一圈不绕完,留一点地方只走一次,而要求剩下两条边只走一次,很显然这两条边一定要挨在一起 。

。。。。。 反正就是个结论。

既然如此我们就记录每个点的度数,把每个点度数对于2的组合数,加起来,就行了,因为边挨在一起一定是有公共点,我们枚举整个公共点,看这个点相连的有多少条边,其中选两条的方案数。

看似没有问题,但这道题还需要考虑自环的情况,对于一条边,如果他是一个自环,那么就没有必须挨在一起的限制。

考虑两个自环作为一条路径的两条只走一次的边,考虑用线把整个图包起来,而自环反正走了和没走一样,只需要把这个图走两边,然后在自环的那两个点各跑一次自环就行了,不需要挨在一起
还需要处理自环加上一条不是自环的边的情况,自环数乘以自环数-1再除以2

也不需要挨在一起,一个是自环,另一个随便选一条边,不用除2
加上这两种情况之后就是答案

要注意图不一定连通,用并查集判一下,注意不是点的连通,而是所有的边要在一个连通块,等价于判断所有边的端点在一个连通块。

#include <iostream>
#include <cstdio>
#include <algorithm>
#define LL long long
using namespace std;

const int N = 100010;

int n, m, idc=0, idx=0;
int head[N], fa[N], cle[N];
LL ans, du[N];

int getfa(int x){
    if(fa[x] == x) return x;
    else return fa[x] = getfa(fa[x]);
}

int main(){
    freopen ("tour.in", "r", stdin);
    freopen ("tour.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++) fa[i] = i;
    for(int i=1; i<=m; i++){
        int u, v; scanf("%d%d", &u, &v);
        if(u != v) du[u]++, du[v]++;
        else du[u]++, cle[++idx] = u;
        int a = getfa(u), b = getfa(v);
        if(a != b) fa[a] = b;
    }
    int ff;
    for(int i=1; i<=n; i++){
        if( du[i] ){
            ff = getfa(i);
            break;
        }
    }
    for(int i=2; i<=n; i++){
        int cc = getfa(i);
        if(ff != cc && du[i]) {
            printf("0\n");//判是否有边不连通 
            return 0;
        }
    }
    for(int i=1; i<=n; i++){
        ans += du[i] * (du[i] - 1) / 2;
    }
    for(int i=1; i<=idx; i++){
        ans += m - du[cle[i]] - idx + 1;//自环与非自环 
    }
    ans += idx * (idx - 1) / 2;//自环与自环 
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值