【Luogu4221】[WC2018] 州区划分

题目链接

题目描述

Sol

一个州合法就是州内点形成的子图中 不存在欧拉回路(一个点也算欧拉回路)。

这个东西显然就状压 dp 一下:
f [ S ] f[S] f[S] 表示当前考虑了 S S S 这个集合内所有点的所有方案满意度之和。
转移就枚举一个子集作为最后选出的一个州
f [ S ] = ( 1 s u m [ S ] ) p ∑ T ⊆ S f [ T ] ∗ g [ S − T ] f[S]=\bigg(\frac{1}{sum[S]}\bigg)^p\sum_{T\subseteq S}f[T]*g[S-T] f[S]=(sum[S]1)pTSf[T]g[ST]

s u m [ S ] sum[S] sum[S] 表示 S S S 内所有城市的人口之和
g [ S ] g[S] g[S] 表示 , 如果 S S S 是一个合法的州,那么 g [ S ] = s u m [ S ] p g[S]=sum[S]^p g[S]=sum[S]p , 否则 g [ S ] = 0 g[S]=0 g[S]=0

后面那个东西就是个子集卷积了,对于两个集合 U , V U,V U,V , 如果 U ∣ V = S U|V=S UV=S U & V = 0 U\&V=0 U&V=0 那么就把他们的积贡献给 S S S

如果不需要满足集合无交的化就是一个或卷积 , 直接 F W T FWT FWT
但是这里要求无交。解决方法就是按照集合大小一个个来做。
因为 如果满足 U ∣ V = S U|V=S UV=S ∣ U ∣ + ∣ V ∣ = ∣ S ∣ |U|+|V|=|S| U+V=S 的话 那么 U U U V V V 就一定无交了,这样转化为大小限制就可以通过清空不合法状态来保证正确性。
每次做完一个大小后把数组中不是当前大小的状态的值清0就可以了。

code:

#include<bits/stdc++.h>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=22;
const int MAXN=1<<(N-1);
const int mod=998244353;
template <typename T> inline void init(T&x){
	x=0;char ch=getchar();bool t=0;
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
	if(t) x=-x;return;
}typedef long long ll;
template <typename T>inline void Inc(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
template <typename T>inline void Dec(T&x,int y){x-=y;if(x <  0) x+=mod;return;}
template <typename T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod) if(k&1) ret=(ll)ret*x%mod;return ret;}
int Sum(int x,int y){x+=y;if(x>=mod) return x-mod;return x;}
int Dif(int x,int y){x-=y;if(x < 0 ) return x+mod;return x;}
int n,m,p;int Ful;
struct edge{
	int to,next;
}a[N*N];int cnt=0;
int head[N],sum[MAXN],Isum[MAXN],w[N],bits[N],number[MAXN];
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
int deg[N];bool vis[N];int stk[N],top=0;
int fa[N];int f[22][MAXN],g[22][MAXN];
inline int find(int x){return fa[x]==x? x:fa[x]=find(fa[x]);}
inline void Merge(int x,int y){int fx=find(x),fy=find(y);if(fx==fy) return;fa[fx]=fy;}
namespace FST{
	inline void DFT(int*A,int n,int f){
		for(int i=1;i<n;i<<=1)
			for(int p=i<<1,j=0;j<n;j+=p)
				for(int k=0;k<i;++k)
					(~f)? Inc(A[i|j|k],A[j|k]):Dec(A[i|j|k],A[j|k]);
		return;
	}
}
int main()
{
	init(n),init(m),init(p);int u,v;
	for(int i=1;i<=m;++i) {init(u),init(v);add(u,v),add(v,u);}
	for(int i=1;i<=n;++i) init(w[i]);sum[0]=0;
	Ful=(1<<n)-1;bits[0]=1;for(int i=1;i<=n;++i) bits[i]=bits[i-1]<<1;
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=Ful;++i) {top=0;number[i]=number[i>>1]+(i&1);
		for(int j=1;j<=n;++j) if(i&bits[j-1]) stk[++top]=j,vis[j]=1,Inc(sum[i],w[j]);
		bool fl=0;sum[i]=fpow(sum[i],p);Isum[i]=fpow(sum[i],mod-2);
		for(int j=1;j<=top;++j) {
			int u=stk[j];
			for(int v,i=head[u];i;i=a[i].next) {v=a[i].to;if(!vis[v]) continue;Merge(u,v);++deg[u];}
			if(deg[u]&1) {fl=1;break;}
		}int rt=find(stk[1]);
		for(int j=2;j<=top;++j) if(find(stk[j])!=rt) {fl=1;break;}
		for(int j=1;j<=top;++j) {int u=stk[j];fa[u]=u,deg[u]=0,vis[u]=0;}
		if(!fl) continue;g[number[i]][i]=sum[i];
	}f[0][0]=1;
	for(int i=0;i<=n;++i) FST::DFT(g[i],bits[n],1);FST::DFT(f[0],bits[n],1);
	for(int i=1;i<=n;++i) {
		for(int j=0;j<i;++j) for(int s=0;s<=Ful;++s) Inc(f[i][s],(ll)f[j][s]*g[i-j][s]%mod);
		FST::DFT(f[i],bits[n],-1);// IDFT
		for(int s=0;s<=Ful;++s) {if(number[s]!=i) f[i][s]=0;else f[i][s]=(ll)f[i][s]*Isum[s]%mod;}
		if(i!=n) FST::DFT(f[i],bits[n],1);
	}
	cout<<f[n][Ful]<<endl;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值