题解 TopCoder14250 HamiltonianPaths
题目大意
给一个\(k\)个点的无向图\(G1\),你将它复制了\(n\)份,得到一个图\(G2\),然后取补图得到\(G2'\)。
求\(G2'\)中哈密尔顿路径数(不是回路数),对\(998244353\)取模。
\(k\leq 14,n\leq 5\times 10^4\)
条件相当于求不能走原图里的边的方案数。考虑容斥,转化为求必须走至少\(i\)条原图里边的方案数。
\(k\)很小,考虑状压。设\(g_{s,i}\)表示走了\(G1\)的点集\(s\),不能走的边构成了\(i\)条链的方案数(一定是链,否则不是哈密尔顿回路)。所以我们先求一个\(f_{s,i}\)表示点集\(s\)里的点构成一条链,且链的一端在\(i\)的方案数。这样就有\(g_{s,i}=\displaystyle\sum_{t\subseteq s,t\neq \varnothing}g_{s\setminus t,i-1}\big(\sum_{j\in t}f_{t,j}\big)\)。
求\(f\)是\(O(2^kk^2)\),预处理\(f_t\)的和后,求\(g\)的复杂度是\(O(3^kk)\)。
现在我们求出了把\(G1\)缩成\(i\)个连通块的方案数\(g_{(1<<k)-1,i}\)。根据前面DP的定义,这里每个连通块其实都是一条链。我们要在\(G2\)中取出若干个这样的连通块,此时这些连通块间是一个完全图。因为根据容斥,非联通块里的边是可以随便选的,既可以选不合法边(原图里的),也可以选合法边(补图里的),这就显然是完全图了。
在完全图中,设有\(x\)个连通块,则它们间哈密尔顿路径的数量就是\(x!\)。容斥时对答案的贡献系数是\((-1)^{nk-x}\),因为共有\(nk-1\)条边,\(x\)个连通块间的\(x-1\)条边是随便选的,剩下的\(nk-x\)条边是我们强制不合法的(连通块内的)。
所以现在我们要做的就是对每个\(1\leq x\leq nk\),求出把\(G2\)缩成\(x\)个联通块的方案数。
\(G2\)就是\(n\)个\(G1\),而\(G1\)内缩成\(x\)个连通块的方案数我们已经求出,就是\(g_{(1<<k)-1,x}\)。相当于我们要做\(n\)次背包,也可以看做是\(n\)次自己卷自己的卷积。我们可以先把这个多项式的点值表示求出来,对每个点值做快速幂,再IDFT回去即可。这个多项式的长度显然是\(nk\),所以复杂度\(O(nk\log {nk})\)。
设卷积后的数组是\(a\),则答案就是\(\displaystyle\sum_{x=1}^{nk}(-1)^{nk-x}\cdot (x!)\cdot a_x\)。
复杂度\(O(2^kk^2+3^kk+nk\log {nk})\)。
参考代码:
//problem:
#include <bits/stdc++.h>
using namespace std;
class HamiltonianPaths{
private:
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int fac[50005*15],invf[50005*15];
vector<int>G[14];
int m,f[1<<14][14],sf[1<<14],g[1<<14][14*14+5];
int a[50005*15*2],rev[50005*15*2];
void ntt(int *a,int n,int flag){
for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1){
int T=pow_mod(3,(MOD-1)/(i<<1));
if(flag==-1)T=pow_mod(T,MOD-2);
for(int j=0;j<n;j+=(i<<1)){
for(int k=0,t=1;k<i;++k,t=(ll)t*T%MOD){
int Nx=a[j+k],Ny=(ll)a[i+j+k]*t%MOD;
a[j+k]=mod1(Nx+Ny);
a[i+j+k]=mod2(Nx-Ny);
}
}
}
if(flag==-1){
int invn=pow_mod(n,MOD-2);
for(int i=0;i<n;++i)a[i]=(ll)a[i]*invn%MOD;
}
}
public:
int countPaths(int n,vector<int> u,vector<int> v,int x){
fac[0]=1;for(int i=1;i<=n*x;++i)fac[i]=(ll)fac[i-1]*i%MOD;
invf[n*x]=pow_mod(fac[n*x],MOD-2);
for(int i=n*x-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;assert(invf[0]==1);
m=u.size();
for(int i=0;i<m;++i)G[u[i]].pb(v[i]),G[v[i]].pb(u[i]);
for(int i=0;i<n;++i)f[1<<i][i]=1;
for(int s=1;s<(1<<n);++s){
for(int i=0;i<n;++i)if((s>>i)&1){
for(int j=0;j<(int)G[i].size();++j)if(!((s>>G[i][j])&1)){
f[s^(1<<G[i][j])][G[i][j]]=mod1(f[s^(1<<G[i][j])][G[i][j]]+f[s][i]);
}
}
}
for(int s=1;s<(1<<n);++s){
for(int i=0;i<n;++i)sf[s]=mod1(sf[s]+f[s][i]);
}
g[0][0]=1;
for(int s=1;s<(1<<n);++s){
for(int i=1;i<=n;++i){
for(int t=s;t;t=((t-1)&s)){
g[s][i]=mod1(g[s][i]+(ll)g[s^t][i-1]*sf[t]%MOD);
}
}
}
for(int i=1;i<=n;++i)a[i]=(ll)g[(1<<n)-1][i]*invf[i]%MOD;
int lim=1,ct=0;
while(lim<=n*x)lim<<=1,ct++;
for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(ct-1));
ntt(a,lim,1);
for(int i=0;i<lim;++i)a[i]=pow_mod(a[i],x);
ntt(a,lim,-1);
int ans=0;
for(int i=0;i<=n*x;++i){
if((n*x-i)&1)ans=mod2(ans-(ll)a[i]*fac[i]%MOD);
else ans=mod1(ans+(ll)a[i]*fac[i]%MOD);
}
return ans;
}
};
//HamiltonianPaths Solver;
//int main() {
// int n,m,x;vector<int>u,v;
// cin>>n>>m;
// for(int i=0;i<m;++i){int t;cin>>t;u.pb(t);}
// for(int i=0;i<m;++i){int t;cin>>t;v.pb(t);}
// cin>>x;
// cout<<Solver.countPaths(n,u,v,x)<<endl;
// return 0;
//}