题目
题意概要
给一个简单有向图,将边重定向,使得无环,则为合法。求所有合法的定向方案中,边反向的数量总和。
数据范围与提示
n
≤
18
n\le 18
n≤18,边数无特别要求,即
0
≤
m
≤
n
(
n
−
1
)
2
0\le m\le\frac{n(n-1)}{2}
0≤m≤2n(n−1) 。
思路
无环?让人想到拓扑序。能不能直接数拓扑序的数量呢?
我们肯定要做到一一对应。即,每个图唯一对应一个拓扑序,每个拓扑序唯一对应一个图。第二点已经做到了:边的方向肯定是拓扑序小的到大的。而第一条就不一定了,比如两个点同时没有入度。
必须规定,编号小的先选。然后接踵而至很多问题——这就是我们要解决的了。
假如某个点 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)=L∪R=S−{x}∑L∩R=Ø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\}
x≥max{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×3i−1,而
x
x
x 的数量是
n
−
i
n-i
n−i,所以实际上复杂度是
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=1∑n(n−i)⋅3i−1=n3n−2i=1∑ni⋅3i−1=n3n−i=0∑n−1(3n−3i)=23n−1
看上去不错,但是因为常数原因,光荣嗝屁了……
还需要最后一个小 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;
}