【UOJ348】【WC2018】州区划分 状压DP FWT

题目大意

  给定一个(n)个点的无向图,对于每种 (n) 个点的划分({S_1,S_2,ldots,S_k}),定义它是合法的,当且仅当每个点都在其中的一个集合中且对于任何的(iin[1,k]),点集(S_i)非空,且导出子图不存在欧拉回路。

  给定数组(w_i),求对于所有合法的划分({s_1,s_2,ldots,s_k}),下面的式子之和

[{(prod_{i=1}^kfrac{sum_{xin S_i}w_x}{sum_{j=1}^isum_{xin S_j}w_x})}^p ]

  (nleq 21)

题解

  先用(O(n^22^n))判断每个点集是否合法,并计算(g(S)={(sum_{xin S}w_x)}^p)。如果(S)不合法。那么(g(S)=0)

  很容易想到一个(O(3^n))的做法。

  设(f(S))为当前选择的集合为(S)的答案

[f(S)=frac{sum_{Tsubseteq S}g(T) f(Ssetminus T)}{g(S)} ]

  可以发现这是一个子集卷积。

  一种可行的做法是把子集卷积转化为子集或卷积。

  定义(widetilde{f})(f)的集合占位幂级数,当且仅当对于所有的(S)满足(widetilde{f}(S))是一个(|S|)次多项式,且([x^{|S|}]widetilde{f}(S)=f(S))

  可以发现,若(p(S)=widetilde f(S)cdot widetilde g(S)),则(p)(f imes g)的占位多项式。

  所以我们可以在(O(n^22^n))内计算子集卷积了。

  回到这道题,我们假设每个城市可以出现在多个州里,记(h_{i,S})为每个州的城市个数之和为(i),每个州的城市的并集为(S)的方案数。那么(F(S)=sum_{i=1}^nh_{i,S}x^i)就是(f(S))的集合占位幂级数。

  所以(f(S)=h_{|S|,S}),状态转移方程是

[h_{i,S}=sum_{j=1}^isum_{|T|=j}sum_{A}[A|T=S]h_{i-j,A}frac{g(T)}{g(S)} ]

  记(G(i)=sum_{|S|=i}g(S)x^S)

  我们先枚举(i),再枚举(j),然后计算(h_i=h_{i-j} imes G(j))

  这里我们可以全程用莫比乌斯变换后的值,卷积一次就是(O(2^n))

  还有,我们这里要除以(g(S)),可以在做完一层((h_i))之后变换回去,除以(g(S)),再变换回来。

  这样时间复杂度就是(O(n^22^n))的了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
    char str[100];
    sprintf(str,"%s.in",s);
    freopen(str,"r",stdin);
    sprintf(str,"%s.out",s);
    freopen(str,"w",stdout);
#endif
}
const ll p=998244353;
ll fp(ll a,ll b)
{
	ll s=1;
	for(;b;b>>=1,a=a*a%p)
		if(b&1)
			s=s*a%p;
	return s;
}
int lx[100010];
int ly[100010];
int n,m,o;
void fwt(int *a)
{
	int i,j;
	for(j=1;j<1<<n;j<<=1)
		for(i=0;i<1<<n;i++)
			if(i&j)
				a[i]=(a[i]+a[i^j])%p;
}
void ifwt(int *a)
{
	int i,j;
	for(j=1;j<1<<n;j<<=1)
		for(i=0;i<1<<n;i++)
			if(i&j)
				a[i]=(a[i]-a[i^j])%p;
}
int f[22][1<<21];
int g[22][1<<21];
ll inv[100010];
ll fw[1<<21];
ll fwi[1<<21];
int w[30];
int fa[100010];
int num[1<<21];
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int c[1<<21];
int d[100010];
int main()
{
	open("walk");
	int i,j,k;
	inv[0]=inv[1]=1;
	for(i=2;i<=10000;i++)
		inv[i]=-p/i*inv[p%i]%p;
	scanf("%d%d%d",&n,&m,&o);
	for(i=1;i<=m;i++)
		scanf("%d%d",&lx[i],&ly[i]);
	for(i=1;i<=n;i++)
		scanf("%d",&w[i]);
	int all=(1<<n)-1;
	for(i=1;i<=all;i++)
	{
		for(j=1;j<=n;j++)
		{
			d[j]=0;
			fa[j]=j;
		}
		for(j=1;j<=m;j++)
			if(((i>>(lx[j]-1))&1)&&((i>>(ly[j]-1))&1))
			{
				d[lx[j]]^=1;
				d[ly[j]]^=1;
				int fx=find(lx[j]);
				int fy=find(ly[j]);
				if(fx!=fy)
					fa[fx]=fy;
			}
		fw[i]=0;
		for(j=1;j<=n;j++)
			if((i>>(j-1))&1)
				fw[i]+=w[j];
		fwi[i]=inv[fw[i]];
		fw[i]=fp(fw[i],o);
		fwi[i]=fp(fwi[i],o);
		int cnt=0;
		c[i]=0;
		for(j=1;j<=n;j++)
			if((i>>(j-1))&1)
			{
				num[i]++;
				if(fa[j]==j)
				{
					cnt++;
					if(cnt>=2)
						c[i]=1;
				}
				if(d[j])
					c[i]=1;
			}
		fw[i]*=c[i];
		g[num[i]][i]=fw[i];
	}
	f[0][0]=1;
	for(i=1;i<=n;i++)
		fwt(g[i]);
	fwt(f[0]);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=i;j++)
			for(k=0;k<=all;k++)
				f[i][k]=(f[i][k]+(ll)f[i-j][k]*g[j][k])%p;
		ifwt(f[i]);
		for(j=0;j<=all;j++)
			f[i][j]=f[i][j]*fwi[j]%p;
		fwt(f[i]);
	}
	ifwt(f[n]);
	ll ans=f[n][all];
	ans=(ans+p)%p;
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/ywwyww/p/8514554.html