2022年中国高校计算机大赛-团队程序设计天梯赛(GPLT)上海理工大学校内选拔赛 O题
题目描述:
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)=
记连通图数量为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)* (不包含第n个点的图的区域有g(n-i)种连通情况,然后从n-1个点选出i-1个)
则f(n)=g(n)-
但这还没到我们熟悉的卷积形式,稍加修改以后:
在令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;
}