上海施工小学 (分治NTT)

文章讲述了在2022年中国高校计算机大赛中,关于一个有n个节点的校园网络,如何在施工导致边被删除后保持连通性的问题。通过数学方法,特别是利用连通图数量的递推关系和NTT算法求解不同删除方案的数量,最后给出了C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2022年中国高校计算机大赛-团队程序设计天梯赛(GPLT)上海理工大学校内选拔赛 O题

题目描述:

链接:登录—专业IT笔试面试备考平台_牛客网

Komorebi就读于上海施工小学。

我们可以把校园看作一张有n个点的无向无权图,其中每个点都是不等价的(宿舍和教学楼当然不能视为同一个点啦)。我们将这些点从1到n标号,在初始时任意两个点对(i,j)之间都有一条无向边,当然,因为是无向边,所以(i,j)和(j,i)之间的边是同一条。换句话说,在初始状态下校园是一个有标号的无向无权完全图。

Komorebi近期发现学校总是在施工,直接影响他的就是施工会带来封路。这对喜欢到处乱逛的Komorebi来说相当不方便,因为封路可以视为在这个图上删去了一条边,他想去一个地方就不得不绕路。
现在学校将在图上删去任意条边,但必须保证删完这些边后整个图是连通的,即任意一个点对(i,j),都可以从点i经过一些点(或直接)到达点j。
一种方案可以认为是一个删去的边的集合。而我们定义两种方案A和B是不同的当且仅当存在一条边E,E∈A&E∉B。
Komorebi想知道学校一共有多少种不同的删除方案呢?请你帮帮他吧!
答案可能会很大,因此你只需要输出答案对998244353取模后的结果即可。

解:

记n个点的图的数量为g(n),最大边数有(n-1)*n/2条,故g(n)=2^{^{n*(n-1)/2}}

记连通图数量为f(n);

我们希望可以通过前面的f(i)求出f(n)

若已知f(1),f(2),f(3).....f(n-1),求f(n)

可以肯定的是f(n)=g(n)-不是连通图的数量

那么可以知道假定不连通的连通区域至少有两块,我们分为包含第n个点的连通区域以及不包含第n个点的连通区域,假定数量分别为i 和n-i ,0<i<n;

则对每一种i有

f(i)*g(n-i)*\binom{n-1}{i-1}   (不包含第n个点的图的区域有g(n-i)种连通情况,然后从n-1个点选出i-1个)

则f(n)=g(n)-\sum_{i=1}^{n-1}f(i)*g(n-i)*\binom{n-1}{i-1}

但这还没到我们熟悉的卷积形式,稍加修改以后:

f(n)=g(n)-(n-1)!\sum_{i=1}^{n-1}(f(i)/(i-1)!)*(g(n-i)/(n-i)!)

在令F(x)=f(x)/(x-1)!,G(x)=g(x)/x!;

然后就可以用分治NTT解决了

代码如下:

#include<iostream>
#include<string.h>
using namespace std;
#define int long long
const int N = 26e5 + 10, M = 1e3 + 10;
const int mod = 998244353, gi[2] = {3, 332748118};
int f[N],g[N],fac[N],invfac[N],F[N],G[N],a[N],b[N];
int q_pow(int a, int b)
{
    int t = 1;
    while (b)
    {
        if (b & 1)t = t * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return t;
}
void Read(int fi[], int len)
{
    int k = 0;
    for (int i = 0; i < len-1; i++)
    {
        int l = len / 2;
        if (k > i)swap(fi[k], fi[i]);
        while (k >= l)k -= l, l /= 2;
        k += l;
    }
}
void NTT(int fi[], int on, int len)
{
    Read(fi, len);
    for (int i = 2; i <= len; i <<= 1)
    {
        int wi = q_pow(gi[on], (mod - 1) / i),wn;
        for (int j = 0; j < len; j += i)
        {
            wn = 1;
            for (int k = j; k < j + i / 2; k++)
            {
                int t1 = fi[k] ,t2= wn * fi[k + i / 2]%mod ;
                fi[k] = (t1 + t2) % mod, fi[k + i / 2] = (t1 - t2 + mod) % mod;
                wn = wn * wi % mod;
            }
        }
    }
    if (on == 1)
    {
        int invlen = q_pow(len, mod - 2);
        for (int i = 0; i < len; i++)
        {
            fi[i] = fi[i] * invlen % mod;
        }
    }
}
void init()
{
    g[0] = 1,fac[0]=invfac[0]=1;
    int chen = 1;
    for (int i = 1; i < N; i++)
    {
        g[i] = g[i - 1] * chen % mod;
        chen = chen * 2 % mod;
        fac[i] = fac[i - 1] * i % mod;
    }
    invfac[N - 1] = q_pow(fac[N - 1], mod - 2);
    G[N - 1] = g[N - 1] * invfac[N - 1] % mod;
    for (int i = N - 2; i >0; i--)
    {
        invfac[i] = invfac[i + 1] * (i + 1) % mod;
        G[i] = g[i] * invfac[i] % mod;
    }
    //cout << G[1] << endl;
}
void solve(int l, int r)
{
    if (l == r)
    {
        f[l] = (g[l] - fac[l-1]*f[l]%mod + mod) % mod;
        F[l] = f[l] * invfac[l - 1] % mod;
        return;
    }
    int mid = l + r >> 1;
    solve(l, mid);
    int len = 1;
    while (len < 2*(r - l+1))len <<= 1;
    a[0] = b[0] = 0;
    for (int i = l; i <= mid; i++)a[i-l+1] = F[i];
    for (int i = 1; i <= r-l; i++)b[i] = G[i];
    for (int i = mid - l + 2; i < len; i++)a[i] = 0;
    for (int i = r-l + 1; i < len; i++)b[i] = 0;
    NTT(a, 0, len), NTT(b, 0, len);
    for (int i = 0; i < len; i++)a[i] = a[i] * b[i] % mod;
    NTT(a, 1, len);
    for (int i = mid + 1 ; i <= r; i++)
    {
        f[i] += a[i-l+1];
        f[i] %= mod;
    }
    solve(mid + 1, r);
}
signed main()
{
    init();
    int n;
    cin >> n;
    solve(1, n);
    cout << f[n] << endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值