删边计数

这是一篇关于无向图的算法题目,要求在保持图连通的前提下,删除一定数量的边,并求解所有可能的删边方案总数。题目提供了一种利用状态压缩和生成树计数的方法来解决,通过考虑环的存在并处理环的两个方向,来避免计数重复。样例给出了不同规模的输入和输出示例。

时间限制 : 1000 MS 空间限制 : 65536 KB

问题描述

给你一个由n个点m条边构成的无向图。要你从图中删除m-n条边,使得剩下的图示连通的。 问总共有多少种删边方案?

输入格式

第一行,两个整数n,m (1≤n≤16,n≤m≤n*(n−1)/2)
接下来m行,每行两个整数x和y,表示点x与点y之间有边相连。
图中没有自环,也没有重边。

输出格式

输出一个整数,表示答案,答案可能很大,mod 998244353再输出。

样例输入 1

4 5
1 2
2 3
3 4
4 1
1 3

样例输出 1

5

样例输入 2

10 18
2 1
3 2
4 1
4 2
4 3
5 2
6 1
6 3
7 3
7 5
8 1
8 5
8 6
9 6
9 8
10 1
10 5
10 6

样例输出 2

16334

样例输入 3

10 34
2 1
3 2
4 2
5 1
5 3
5 4
6 2
6 3
6 4
6 5
7 1
7 2
7 3
7 4
7 6
8 1
8 2
8 4
8 5
8 7
9 1
9 3
9 5
9 6
9 7
9 8
10 1
10 2
10 4
10 5
10 6
10 7
10 8
10 9

样例输出 3

38541686

题解

本题要求的即为原图生成树加上一条边的数量。如果直接算出所有生成树再加边上去的话显然会算重很多情况。我们把本题的特殊点——存在唯一一个环作为突破口。我们发现本题数据范围并不大,因此可用状态压缩统计出环的数量,设状态f[i][S]f[i][S]表示状态SS的路径以i为终点的数量,SS最低位的1为路径的起点(注意由于环可以两个方向遍历,最后答案要除以2)。处理完环后,把环缩点再进行生成树计数即可。

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll a,b,n,m,ans,tot,id[20],G[20][20],map[20][20];
ll sum[1<<17],f[20][1<<17];
ll Gauss(ll n)
{
    ll temp=1,flag=0;
    for(ll i=1;i<=n;i++)
    {
        for(ll j=i+1;j<=n;j++)
            while(G[j][i])
            {
                ll t=G[i][i]/G[j][i];
                for(ll k=i;k<=n;k++) G[i][k]=(G[i][k]-t*G[j][k]%mod+mod)%mod;
                swap(G[i],G[j]),flag^=1;
            }
        temp=temp*G[i][i]%mod;
    }
    if(flag) temp=mod-temp;
    return (temp%mod+mod)%mod;
}
ll ksm(ll a,ll b)
{
    ll temp=1;
    while(b)
    {
        if(b&1) temp=temp*a%mod;
        b>>=1,a=a*a%mod;
    }
    return temp;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=m;i++) scanf("%lld%lld",&a,&b),map[a][b]++,map[b][a]++;
    for(ll S=1;S<(1<<n);S++)
    {
        ll cnt=__builtin_popcount(S),u=__builtin_ctz(S)+1;
        if(cnt==1) f[u][S]=1;
        for(ll v=u;v<=n;v++)
        {
            if(!f[v][S]) continue;
            for(ll i=u+1;i<=n;i++)
                if(!(S&(1<<(i-1)))&&map[i][v]) 
                    f[i][S|(1<<(i-1))]=(f[i][S|(1<<(i-1))]+f[v][S])%mod;
            if(cnt>=3&&map[u][v]) sum[S]=(sum[S]+f[v][S])%mod;
        }
    }
    for(ll S=1;S<(1<<n);S++)
    {
        if(!sum[S]) continue;
        memset(G,0,sizeof(G)),tot=1;
        for(ll i=1;i<=n;i++)
            if(S&(1<<(i-1))) id[i]=1;
            else id[i]=++tot;
        for(ll i=1;i<=n;i++)
            for(ll j=1;j<=n;j++)
                if(id[i]!=id[j]&&map[i][j]) G[id[i]][id[j]]--,G[id[i]][id[i]]++;
        ans=(ans+Gauss(tot-1)*sum[S]%mod+mod)%mod;
    }
    printf("%lld\n",ans*ksm(2,mod-2)%mod);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值