[CF1193A]Amusement Park

122 篇文章 0 订阅
23 篇文章 0 订阅

题目

传送门 to CF

题意概要
给一个简单有向图,将边重定向,使得无环,则为合法。求所有合法的定向方案中,边反向的数量总和。

数据范围与提示
n ≤ 18 n\le 18 n18,边数无特别要求,即 0 ≤ m ≤ n ( n − 1 ) 2 0\le m\le\frac{n(n-1)}{2} 0m2n(n1)

思路

无环?让人想到拓扑序。能不能直接数拓扑序的数量呢?

我们肯定要做到一一对应。即,每个图唯一对应一个拓扑序,每个拓扑序唯一对应一个图。第二点已经做到了:边的方向肯定是拓扑序小的到大的。而第一条就不一定了,比如两个点同时没有入度。

必须规定,编号小的先选。然后接踵而至很多问题——这就是我们要解决的了。

假如某个点 x x x,拓扑序比 y    ( y < x ) y\;(y<x) y(y<x) 还要小(类似逆序对),那就必须是当初 y y y 并非入度为零。也就是说,在二者之间的某个点与 y y y 相邻(“二者之间” 包含 x x x 不包含 y y y )。

你会发现,如果仍然从左往右考虑拓扑序,这玩意儿就做不了。何必拘泥于从左往右?我们用 笛卡尔树 的方式,每次确定区间最大值。(为什么会这样想呢?——因为上面要找 左边第一个比自己大的,这就是单调栈,和笛卡尔树的建树是雷同的。)

显然最大值的右侧都不会找到左侧的点。也就是说,左边的点对右边的点没有任何限制条件。独立子问题

那么右边的点到底满足什么条件呢?设最大值是 t t t,显然 t t t 与右侧所有点都是 “逆序对”,那么右侧第一个点与 t t t 相邻,右侧第二个点与 t t t 或者第一个点相邻,以此类推。你会发现这是一个类似 p r i m \tt prim prim 的过程。这应该是唯一的约束条件。

要考虑约束条件的叠加,即并非笛卡尔树第一层的情况。若已经有一个要求是,接下来的点的选择顺序必须类似于 x x x 为起点的 p r i m \tt prim prim,接下来又枚举 max ⁡ = t \max=t max=t,能行吗?

事实上完全可以。考虑枚举 t t t 左边和右边分别是哪些点,设为集合 L , R L,R L,R,那么需要满足的条件很简单:

  • L L L 类似于 x x x 为起点的 p r i m \tt prim prim
  • 在无向图中, L L L 中某一个点与 t t t 相邻,或者 x , t x,t x,t 相邻。
  • R R R 类似于 t t t 为起点的 p r i m \tt prim prim

不难发现,这样一来,尽管 R R R 没有考虑 x x x,其实整个方案类似于 x x x 为起点的 p r i m \tt prim prim

所以 d p \tt dp dp 已经可行了。 L , R L,R L,R 之间的连边也可以直接计算,乘一下 c n t \rm cnt cnt 就可以了。形式化一点,记 f ( S , x ) f(S,x) f(S,x) 为上面这个东西, G ( x ) G(x) G(x) x x x 的邻点,令 t = max ⁡ { S } t=\max\{S\} t=max{S},那么
f ( S , x ) = ∑ L ∪ R = S − { x } L ∩ R = Ø f ( L , x ) ⊕ f ( R , t ) ⊕ w ( L , R ) f(S,x)=\sum_{L\cup R=S-\{x\}}^{L\cap R=\text{\O}}f(L,x)\oplus f(R,t)\oplus w(L,R) f(S,x)=LR=S{x}LR=Øf(L,x)f(R,t)w(L,R)

为什么是 ⊕ \oplus 符号呢?因为并不是简单相加,需要乘 c n t \rm cnt cnt,当然那都是细节了。

这东西看上去复杂度是 O ( n 3 n ) \mathcal O(n3^n) O(n3n) 的。真的吗?一个隐式条件是 x ≥ max ⁡ { S } x\ge\max\{S\} xmax{S},想必大家已经看出来了。考虑枚举 max ⁡ { S } \max\{S\} max{S} 来计算一下复杂度。从 1 1 1 开始给点编号,如果 max ⁡ { S } = i \max\{S\}=i max{S}=i,那么 ⟨ S , L ⟩ \langle S,L\rangle S,L 总数量是 2 × 3 i − 1 2\times3^{i-1} 2×3i1,而 x x x 的数量是 n − i n-i ni,所以实际上复杂度是
2 ∑ i = 1 n ( n − i ) ⋅ 3 i − 1 = n 3 n − 2 ∑ i = 1 n i ⋅ 3 i − 1 = n 3 n − ∑ i = 0 n − 1 ( 3 n − 3 i ) = 3 n − 1 2 \begin{aligned} 2\sum_{i=1}^{n}(n-i)\cdot 3^{i-1} &=n3^n-2\sum_{i=1}^{n}i\cdot 3^{i-1}\\ &=n3^n-\sum_{i=0}^{n-1}(3^n-3^i)\\ &={3^{n}-1\over 2} \end{aligned} 2i=1n(ni)3i1=n3n2i=1ni3i1=n3ni=0n1(3n3i)=23n1

看上去不错,但是因为常数原因,光荣嗝屁了……

还需要最后一个小 t r i c k \rm trick trick:将所有边翻转,仍然是合法方案,所以一条边恰好在一半的方案中出现过,所以只需要求方案数。

代码

#include <bits/stdc++.h>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int_ x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 20;
int g[MaxN], n, logtwo[1<<MaxN];
void prepare(){
	rep(i,0,n-1) g[i] |= 1<<n;
	rep(i,0,n) logtwo[1<<i] = i;
}

const int Mod = 998244353;
inline void add(int &x,const int &y){
	(x += y) >= Mod ? (x -= Mod) : 0;
}
int cnt[MaxN][1<<MaxN], m;
bool vis[MaxN][1<<MaxN];
inline void dfs(int S,int x){
	if(!S) return void(cnt[x][S] = 1);
	if(vis[x][S]) return ; vis[x][S] = 1;
	int t = logtwo[S&-S]; S ^= (S&-S);
	for(int L=S; true; L=(L-1)&S){
		if(!(g[t]&L) && !(g[t]>>x&1)){
			if(L == 0) break;
			continue; // not like-prim
		}
		dfs(L,x), dfs(S^L,t); // needed status
		int ncnt = 1ll*cnt[x][L]*cnt[t][S^L]%Mod;
		add(cnt[x][S^(1<<t)],ncnt); // this proposal
		if(L == 0) break; // [0,S]
	}
}

int main(){
	n = readint(), m = readint();
	rep(i,1,m){
		int a = readint()-1;
		int b = readint()-1;
		g[a] |= 1<<b, g[b] |= 1<<a;
	}
	prepare(); dfs((1<<n)-1,n);
	const int inv2 = (Mod+1)>>1;
	m = 1ll*m*inv2%Mod*cnt[n][(1<<n)-1]%Mod;
	printf("%d\n",m);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值