哈集幂基础练习题(qoj5411&P11734)
qoj5411
首先考虑对一个集合构成一条链计数,记 \(f_{i,S}\) 表示这条链最前面的点为 \(i\),构成集合为 \(S\) 的方案数,而最终要求的是若干条起点是 \(s\) 的子节点,且终点可以直接到 \(t\) 的链的并,用集合幂级数表示这个过程:
\[\begin{aligned}
f_{u}=\sum \frac{f^{i}_{u}}{i !} &= \exp(f_{u})
\end{aligned}
\]
直接做是 \(O(n^3 2^n)\),肯定过不了,于是考虑令 \(h_{S}=\sum\limits_{i=1}^n f_{i,S}\),然后对 \(h\) 做 \(exp\) ,最后求答案,其实是对于两个 \(x,y,x\&y=0\),\(ans \leftarrow f_{u,x}*h_y\),注意到可以对 \(h\) 求一次高维后缀和,于是 \(ans=\sum\limits_{T \in U} f_{u,T}*h_{U-T}\)。
时间复杂度为 \(O(n^2 2^n)\),实际上是可以过的,因为可以把 \(s,t\) 完全不列进状态里。
一些细节,有大量重边,要用邻接矩阵存图,并且注意到可以有 \(s \rightarrow t\) 的边,所以要把这部分贡献乘到答案里。
const int mod=998244353;
int n,m,q,S,s,t,id[maxn];
ll inv[maxn],f[maxn][maxm],h[maxn][maxm],w[maxm],is[maxn],g[maxn][maxn];
void add(ll &x,ll y){x+=y,x>=mod&&(x-=mod);}
ll qpow(ll a,int num=mod-2)
{
ll ans=1;
for(;num;a=a*a%mod,num>>=1)
(num&1)&&(ans=ans*a%mod);
return ans;
}
void FMT(ll *f,int op){
int i,j;
for(j=0;j<(n-2);++j)for(i=0;i<S;++i)if(i>>j&1)
op?add(f[i],f[i^(1<<j)]):add(f[i],mod-f[i^(1<<j)]);
}
void exp(ll *f,ll *g){
int i,j;g[0]=1;
for(i=1;i<=n-2;++i){
for(j=1;j<=i;++j)add(g[i],g[i-j]*f[j]%mod*j%mod);
g[i]=g[i]*inv[i]%mod;
}
}
int main()
{
int i,u,v;
n=read(),S=(1<<n-2),m=read(),s=read(),t=read();
for(i=1;i<=n;++i)id[i]=1<<(i-1-(i>=s)-(i>=t));
for(i=1;i<=n;++i)inv[i]=qpow(i);
for(i=1;i<=m;++i){
u=read(),v=read(),add(g[u][v],1);
if(u==s)add(is[v],1);
}
for(i=1;i<=n;++i)if(i!=s&&i!=t)add(f[i][id[i]],g[i][t]);
for(i=0;i<S;++i)for(u=1;u<=n;++u)if(u!=s&&u!=t&&(id[u]&i))
for(v=1;v<=n;++v)if(v!=s&&v!=t&&(id[v]&i)==0)
add(f[v][i^id[v]],f[u][i]*g[v][u]%mod);
for(i=0;i<S;++i)
for(u=1;u<=n;++u)if(i&id[u]){
add(h[__builtin_popcount(i)][i],f[u][i]*is[u]%mod);
}
for(i=0;i<=n;++i)FMT(h[i],1);
for(i=0;i<S;++i){
ll g[maxn],t[maxn];
for(v=0;v<=n;++v)g[v]=h[v][i],t[v]=0;
exp(g,t);
for(v=0;v<=n;++v)h[v][i]=t[v];
}
for(i=0;i<=n;++i)FMT(h[i],0);
for(i=0;i<S;++i)w[i]=h[__builtin_popcount(i)][i];
FMT(w,1);
ll W=qpow(2,is[t]);
q=read();
while(q--)
{
u=read();
if(!is[u]){
puts("0");
continue;
}
if(u==t){
printf("%lld\n",(W-1+mod)%mod*w[S-1]%mod);
continue;
}
ll ans=0;
for(i=0;i<S;++i)add(ans,f[u][i]%mod*w[(S-1)^i]%mod);
ans=ans*is[u]%mod*W%mod;
printf("%lld\n",ans);
}
return 0;
}
P11734
考虑先对一个集合求它的连通图个数。
设 \(f_S\) 表示集合 \(S\) 为连通图的方案数, \(g=\sum \frac{f^i}{i !}=\exp(f)\),而这里 \(g\) 是好求的,具体的 \(g_S=2^{\sum\limits_{u,v \in S ,(u,v) \in E}}\),所以有 \(f=\ln(g)\)。
接着考虑题目要求的,即 \(F=\sum \frac{i! \times f^i}{i !}=\frac{1}{1-f}\),再做一遍集合幂级数求逆即可。
int n,m,g[maxn][maxn];
ll in[maxn],w[maxm],f[maxn][maxm],h[maxn],t[maxn];
void add(ll &x,ll y){x+=y,x>=mod&&(x-=mod);}
ll qpow(ll a,int num=mod-2)
{
ll ans=1;
for(;num;a=a*a%mod,num>>=1)
(num&1)&&(ans=ans*a%mod);
return ans;
}
void FMT(ll *f,int op){
int i,j;
for(j=0;j<n;++j)for(i=0;i<(1<<n);++i)if(i>>j&1)
op?add(f[i],f[i^(1<<j)]):add(f[i],mod-f[i^(1<<j)]);
}
void inv(ll *f,ll *g){
int i,j;ll w=mod-qpow(f[0]);g[0]=mod-w;
for(i=1;i<=n;++i){
for(j=1;j<=i;++j)add(g[i],g[i-j]*f[j]%mod);
g[i]=g[i]*w%mod;
}
}
void ln(ll *f,ll *g){
int i,j;g[0]=0;
for(i=1;i<=n;++i){
for(j=1;j<i;++j)add(g[i],g[j]*f[i-j]%mod*j%mod);
g[i]=(f[i]+mod-g[i]*in[i]%mod)%mod;
}
}
int main()
{
int i,j,k,u,v;
n=read(),m=read();
for(i=1;i<=n;++i)in[i]=qpow(i);
for(i=1;i<=m;++i)u=read()-1,v=read()-1,++g[u][v],++g[v][u];
for(i=0;i<(1<<n);++i)for(u=0;u<n;++u)if(i>>u&1)for(v=u+1;v<n;++v)if(i>>v&1)
add(w[i],g[u][v]);
for(i=0;i<(1<<n);++i)f[__builtin_popcount(i)][i]=qpow(2,w[i]);
for(i=0;i<=n;++i)FMT(f[i],1);
for(i=0;i<(1<<n);++i){
for(j=0;j<=n;++j)h[j]=f[j][i],t[j]=0;
ln(h,t);
for(j=0;j<=n;++j)f[j][i]=t[j];
}
for(i=0;i<(1<<n);++i){
for(j=0;j<=n;++j)h[j]=(mod+1-f[j][i])%mod,t[j]=0;
inv(h,t);
for(j=0;j<=n;++j)f[j][i]=t[j];
}
for(i=0;i<=n;++i)FMT(f[i],0);
// for(i=0;i<(1<<n);++i)bug("!",i,f[__builtin_popcount(i)][i]);
printf("%lld",f[n][(1<<n)-1]);
return 0;
}

浙公网安备 33010602011771号