I 杰哥再找对象
题解
我们可以通过枚举结点 和结点 所在的连通块中结点的个数计算,设答案为 ,则:
解释: 表示 与 连通的带标号的无向图的个数。 表示 个带标号的点的无向连通图的个数。 表示 个带标号的点的无向图的个数。上式可以理解为,枚举 与 所在连通块的结点数 ,这 个结点中有两个分别是 和 ,因此需要在剩下的 个结点里选取 个结点,共 种方案,然后这 个结点需要连通,共有 种方案,剩下的 个结点不影响答案,因此就是 个带标号的结点的无向图的个数,共 个,再根据乘法原理和加法原理即可得到上式。
令 ,那么 就能从 开始,再将组合数展开,得:
我们可以发现求和的每一项同时含有 和 。这个形式明显是两个多项式的卷积,因此,我们只需要把序列 和序列 计算出来之后,再求卷积,最后乘 即可得到答案。
现在问题变成了求解序列 和序列 ,即 个带标号的点的无向连通图的个数和无向图的个数。我们知道 个点的完全图存在 条边,对于每一条边,取或不取,对无向图的个数都有贡献,故 。
接下来求解 :
我们利用指数生成函数。
令:
求导得:
这四个函数之后会用到。
思考无向连通图与无向图的关系,和上面类似,可以枚举结点 所在的连通块的结点数 ,可得:
解释:计算 个带标号的结点的无向图个数,可以枚举与结点 所在连通块的结点数 ,这 个结点中有一个是结点 ,因此,需要在剩下的 个结点选择 个结点,共有 种方案,这 个结点需要连通,共 种方案,剩下的 个结点不影响答案,就是 个带标号的结点的无向图的个数,共 个,在根据乘法原理和加法原理得到上式。
还是将组合数展开,得:
两边同时除以的 ,再乘 ,得:
将前四个函数带入上式,解微分方程:
这样将 求对数即可得到 ,自然就得到了 。这道题就完成了。
预处理 到 的阶乘、逆元和阶乘的逆元, 计算 , 得到 , 求对数计算 , 计算 ,再 计算卷积得到 到 的所有答案, 输出答案,总时间复杂度 。
std:
#include <bits/stdc++.h>
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(0),cin.tie(0)
#define endl "\n"
using namespace std;
typedef long long ll ;
typedef unsigned long long ull ;
const int mod97=1e9+7, mod33=998244353, mod99=1e9+9;
const int MAX=2e5+10, inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int drc[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
const ll P[5]={mod33, (5LL<<25)+1, (17LL<<27)+1, (7LL<<52)+1, (29LL<<57)+1};
const int NUM=22;
const double PI = acos(-1.0);
const double eps = 1e-7;
int n;
ll wn[NUM];
ll fac[MAX>>1], inv[MAX>>1], inv_f[MAX>>1];
ll A[MAX<<1], B[MAX<<1];
ll ans[MAX<<1];
ull Pow(ull a,ull n,ull mod){
ull ans = 1;
while(n){
if(n & 1) ans = ans * a % mod;
a = a * a % mod;
n >>= 1;
}
return ans;
}
void Get_Wn(){
int t = 1;
for(int i = 0; i < NUM; ++i){
wn[i] = Pow(3, (mod33 - 1) / t, mod33);
t <<= 1;
}
}
void Rader(ll a[], int len){
int j = len >> 1;
for(int i = 1; i < len - 1; ++i) {
if(i < j) swap(a[i], a[j]);
int k = len >> 1;
while(j >= k) {
j -= k;
k >>= 1;
}
if(j < k) j += k;
}
}
void NTT(ll a[], int len, int on) {
Rader(a, len);
int id = 0;
for(int h = 2; h <= len; h <<= 1) {
id++;
for(int j = 0; j < len; j += h){
ll w = 1;
int up = j + (h >> 1);
for(int k = j; k < up; ++k) {
int kk = k + (h >> 1);
ll u = a[k];
ll t = w * a[kk] % mod33;
a[k ] = u + t; if(a[k ] >= mod33) a[k ] = a[k ] - mod33;
a[kk] = u - t; if(a[kk] < 0 ) a[kk] = a[kk] + mod33;
w = w * wn[id] % mod33;
}
}
}
if(on == -1){
int up = len >> 1;
for(int i = 1; i < up; ++i)
swap(a[i], a[len - i]);
int Inv = Pow(len, mod33 - 2, mod33);
for(int i = 0; i < len; ++i)
a[i] = a[i] * Inv % mod33;
}
}
void PolyInv(const ll a[], int n, ll b[]) {
if(n == 1) { b[0] = Pow(a[0], mod33 - 2, mod33); return ; }
int up = (n + 1) >> 1;
PolyInv(a, up, b);
int lim = 1; while(lim < (n << 1)) lim <<= 1;
ll *c= new ll[lim]{};
memcpy(c, a, n * sizeof(ll));
memset(b + n, 0, (lim - n) * sizeof(ll));
NTT(c, lim, 1), NTT(b, lim, 1);
for(int i = 0; i < lim; ++i) {
b[i] = (2LL - c[i] * b[i]) % mod33 * b[i] % mod33;
if(b[i] < 0) b[i] += mod33;
}
NTT(b, lim, -1);
memset(b + n, 0, (lim - n) * sizeof(ll));
delete [] c;
}
void PolyLn(const ll a[], int n, ll b[]){
int lim = 1; while(lim <= (n << 1)) lim <<= 1;
ll *aa= new ll[lim]{}, *c= new ll[lim]{};
for(int i = 1; i < n; ++i) aa[i-1] = a[i] * i % mod33;
PolyInv(a, n, c);
NTT(aa, lim, 1), NTT(c, lim, 1);
for(int i = 0; i < lim; ++i) b[i] = aa[i] * c[i] % mod33;
NTT(b, lim, -1);
for(int i = n - 1; i; --i) b[i] = b[i-1] * inv[i] % mod33;
memset(b + n, 0, (lim - n) * sizeof(ll)); b[0] = 0;
delete []aa, delete []c;
}
void Conv(const ll a[], const ll b[], int n, ll c[]) {
ll *ta= new ll[n], *tb= new ll[n];
for(int i = 0; i < n; ++i) ta[i] = a[i], tb[i] = b[i];
NTT(ta, n, 1);
NTT(tb, n, 1);
for(int i = 0; i < n; ++i)
c[i] = ta[i] * tb[i] % mod33;
NTT(c, n, -1);
delete []ta, delete []tb;
}
void GetFac_Inv(){
fac[0]=inv[1]=1;
for(int i=1; i<=100000; ++i) fac[i] = fac[i-1] * i % mod33; // 阶乘
inv_f[100000] = Pow(fac[100000], mod33-2, mod33);
for(int i=99999; ~i; --i) inv_f[i] = inv_f[i+1] * (i+1) % mod33; // 阶乘的逆元
for(int i=2; i<=100000; ++i) inv[i] = mod33 - mod33 / i * inv[mod33%i] % mod33; // 逆元
}
void Init(){
Get_Wn();
GetFac_Inv();
for(int i=0; i<=100000; ++i){
A[i] = Pow(2, (1LL * i * (i-1) >> 1) % (mod33-1), mod33) * inv_f[i] % mod33;
// 代码写的是 a[n],其实是题解中的 c[n]/n!
}
PolyLn(A, 100001, B); // B(x) ≡ ln(C(x)) (mod x^n)
B[0] = B[1] = 0; // 别忘记 b[0] = b[1] = 0
// 下面两个是求解 a[n] 的表达式中的 b[n]/(n-2)! 和 c[n]/n!
for(int i=2; i<=100000; ++i){
B[i] = B[i] * fac[i] % mod33 * inv_f[i-2] % mod33;
}
for(int i=0; i<=100000; ++i){
A[i] = Pow(2, (1LL * i* (i-1) >> 1) % (mod33-1), mod33) * inv_f[i] % mod33;
}
int lim = 1; while(lim <= 200000) lim <<= 1;
Conv(A, B, lim, ans); // 求卷积存入 ans[n]
ans[1] = 1; // 依题意,ans[1] = 1
for(int i=2; i<=100000; ++i) ans[i] = ans[i] * fac[i-2] % mod33; // 别忘记最后乘 (n-2)!
}
int main(){
Init(); // 预处理所有答案
int t ;
cin >> t;
while(t--){
cin >> n;
cout << ans[n] << endl;
}
return 0;
}