[CEOI 2019] Amusement Park

题意

给出 $n,n \le 18$ 个点的有向图(无自环重边),可以选择一些边使之反向,问所有使该图变为DAG(有向无环图)的所有方案取反边的数量之和。答案对 $998244353$ 取模。

性质

观察到对于某个合法方案 $P$,将其所有边取反后也是一种合法方案,$P$ 与 $P$ 反两种方案是一一对应的,且和为图上边的数量 $m$。则最后的合法方案数 $num$ 一定是偶数,且最终答案为 $num/2*m$。

做法

发现 $n$ 很小考虑状压dp,设 $f_p$ 为状态 $p$ 所含点构成的诱导子图无环的方案数,即DAG,则可以知道一定会有一些点没有入边,称为零度点。那么可以枚举零度点转移,一个集合是合法的零度点集合,当且仅当集合内部点之间不存在边相连。可以对于每个点维护一个数组用二进制位 $1/0$ 维护是/否与其相连,然后正常状压dp维护点 $p$ 集合与哪些点相连,要求一个集合状态可以作为合法零度点集转移当且仅当,枚举集合内每个点,要求该点代表所连点的状态与当前集合去掉该点之后的点集不存在公共子集。

现在转移,枚举子集作为零度点集合,零度点集向外连边的方案是恒定的,然后把剩下补集的方案数直接加上。
但这是错的,因为我们没有要求补集内不存在零度点,可要是加上该限制就不好转移。
所以要容斥,对于集合 $S$,$T$ 是其一个合法零度点子集,对于 $I\subsetneqq T$,$I$ 也会对 $S$ 有贡献会把 $T$ 算重。

$ f_S = \sum_{T \subsetneqq S}(-1)^{|T|-1}f_T$

对于容斥系数的证明我们考虑要求的每个方案,假设 $S$ 为其的所有零度点包含集合,$c(S)$ 为答案系数,那么种方案的 $S$ 在实际计算时候会有 $c(S) = \sum_{T \subsetneqq S} = 2^{|S|}-1$的贡献,我们想要只枚举 $S$ 而不重复计算 $S$ 的子集,所以我们需要答案为 $ans = \sum{c(S)f_S}$,其中 ,我们想令 $c(S)=1$,考虑容斥,$\sum_{T\subsetneqq S}(-1)^{|T|} = 0$,$\sum_{T\subsetneqq S,T \neq \emptyset}(-1)^{|T|} = -1$,同乘 $-1$,$\sum_{T\subsetneqq S,T \neq \emptyset}(-1)^{|T|-1} = 1$。所以容斥系数是奇加偶减。

#include<bits/stdc++.h>
using namespace std;
const int N = 20,M = 1<<N;
const int mod = 998244353;
#define ll long long
int qpow(int a,int b){
	int ret=1;
	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
	return ret;
}
int n,m;
int b[N],up;
ll ans;
int x,y;
ll f[M];
int vis[M],pct[M],v[M];



int main(){
	ios::sync_with_stdio(false);
	cin.tie();
	cin >> n >> m;
	for(int i = 1;i < (1<<n);++i)pct[i]=pct[i&(i-1)]+1;
	for(int i = 1;i <= m;++i){
		cin >> x >> y;
		v[x-1]|=(1<<(y-1));
		v[y-1]|=(1<<(x-1));
	}
	vis[0]=1;
	for(int i = 1;i < (1<<n);++i){
		for(int j = 0;j < n;++j){
			if(((i>>j)&1) && (!(v[j] & i)))vis[i] |= vis[i-(1<<j)];
		}
	}
	f[0] = 1;
	for(int i = 1;i < (1<<n);++i){
		for(int j = i;j;j=(j-1)&i){
			if(vis[j]){
				if(pct[j]&1)f[i]=(f[i]+f[i-j])%mod;
				else f[i]=(f[i]+mod-f[i-j])%mod;
			}
		}
	}
	printf("%lld\n",((f[(1<<n)-1]*m)%mod*qpow(2,mod-2))%mod);
	return 0;
} 
posted @ 2026-07-23 20:25  依绫  阅读(6)  评论(0)    收藏  举报