tugetの妙妙屋

Loading...

哈集幂基础练习题(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;
}
posted @ 2026-01-07 20:44  tuget  阅读(0)  评论(0)    收藏  举报