[CEOI2019] Amusement Park
题外话。昨天学了二项式反演,感觉好玄乎的亚子。其实蒟蒻觉得二项式反演就是为了证明小学奥数的那个容斥原理的系数为什么是 \((-1)^{|S|}\) ,目前为止还没看到它的其它用处。既然如此,记个结论就好啦。
说回题目。首先我们需要思考的是最后得到的有向无环图到底应该是什么样子的。正如其它题解所说,假如能用 \(i\) 次变化变换出一个合法的图,那么必然可以用 \(m-i\) 次变化变换出前者的反图。于是会发现最后的答案会等于变换出合法图的方案数乘上 \(\frac{m}{2}\) 。
然后考虑如何求方案数。点数很小考虑状压。用 \(f(S)\) 来代表某个点集形成有向无环图的方案数,根据拓扑排序的经验,这个有向无环图肯定会有一些出度为 \(0\) 的点,而删去这些点剩下的图也应该是一个有向无环图。所以考虑枚举这些点的点集作为转移的依据。就这样转移的话复杂度是 \(O(3^n)\) 的,复杂度证明见这个帖子,用组合意义或者纯推柿子都可以证。
最后来考虑重复的问题。举个栗子。假如有一张图,3 个点,连边方式是 3 和其它两个点有边。那么会发现枚举 \(\{1,2\}\) 集合时和枚举到 \(\{1\}\) 集合时会有重复情况,其中之一是 \(\{3\leftarrow 1,3\leftarrow 2\}\) 的方案,毕竟这个方案满足两个集合内元素入度为 0 ,所以会被重复计算。
既然有重复就想到通过容斥来解决问题。这一部分其它题解有详细阐述,不再多说。
最后提醒一句,这种题目要注意取模。
#include<bits/stdc++.h>
//#define zczc
#define int long long
const int mod=998244353;
const int N=18;
const int S=1<<N;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
inline int qpow(int s1,int s2){
if(s2==1)return s1;
int an=qpow(s1,s2>>1);
if(s2&1)return an*an%mod*s1%mod;
else return an*an%mod;
}
int m,n,a[N*N],b[N*N],f[S],cnt[S];
bool c[S];
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=n;i++){
read(a[i]);read(b[i]);
a[i]--,b[i]--;
}
for(int i=1;i<(1<<m);i++){
cnt[i]=cnt[(i>>1)]+(i&1);
for(int j=1;j<=n;j++){
if((i&(1<<a[j]))&&(i&(1<<b[j])))c[i]=true;
}
}
f[0]=1;
for(int i=1;i<(1<<m);i++){
for(int j=i;j;j=(j-1)&i){
if(c[j])continue;
f[i]=(f[i]+f[i-j]*((cnt[j]&1)?1:-1)%mod)%mod;
}
}
printf("%lld",(f[(1<<m)-1]*qpow(2,mod-2)%mod*n%mod+mod)%mod);
return 0;
}
一如既往,万事胜意