题目
题目描述
对于一个
n
n
n 个点
m
m
m 条边的无重边无向图,求有多少种给边定向的方式,使得存在至少一个点
u
u
u 满足
1
,
2
1,2
1,2 均可到达
u
u
u 。
数据范围与提示
n
≤
15
,
m
≤
n
(
n
−
1
)
2
n\le 15,\;m\le\frac{n(n-1)}{2}
n≤15,m≤2n(n−1) 。
思路
首先容斥,求不存在 u u u 使得 1 , 2 1,2 1,2 均可到达它。
考虑 1 , 2 1,2 1,2 在有向图中可以到达的点集分别是 S 1 , S 2 S_1,S_2 S1,S2,根据其定义, S 1 , S 2 S_1,S_2 S1,S2 与外部的连边都是指向 S S S 的。进而有, S 1 , S 2 S_1,S_2 S1,S2 之间没有连边。
记 S 3 = U − S 1 − S 2 S_3={\Bbb U}-S_1-S_2 S3=U−S1−S2,可以预处理出 h S h_S hS 表示 S S S 对应的导出子图的边乱选的方案数。只要再求出 f S , g S f_{S},g_S fS,gS 分别表示, S S S 对应的导出子图有多少种定向的方式,使得 1 / 2 1/2 1/2 能够到达所有点,我们就可以用 f S 1 ⋅ g S 2 ⋅ h S 3 f_{S_1}\cdot g_{S_2}\cdot h_{S_3} fS1⋅gS2⋅hS3 很轻松的计算出答案。
怎么求
f
f
f 呢?容斥,考虑有哪些点是
1
1
1 到达不了的。那么有
f
S
=
h
S
−
∑
s
⫋
S
h
S
−
s
⋅
f
s
f_S=h_S-\sum_{s\subsetneqq S}h_{S-s}\cdot f_{s}
fS=hS−s⫋S∑hS−s⋅fs
总是
O
(
3
n
)
\mathcal O(3^n)
O(3n) 的过程。于是这道题就做完了。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
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;
}
const int MaxN = 16;
const int Mod = 1e9+7;
int f[1<<MaxN], g[1<<MaxN], h[1<<MaxN];
int grap[MaxN], cnt[1<<MaxN];
int G[1<<MaxN], n, m;
int main(){
n = readint(), m = readint(); readint();
for(int i=0,u,v; i<m; ++i){
u = readint()-1, v = readint()-1;
grap[u] |= 1<<v, grap[v] |= 1<<u;
}
for(int i=1; i<(1<<n); ++i){
cnt[i] = cnt[i^(i&-i)]+1;
int id = 0; // lowbit
for(; !(i>>id&1); ++id);
G[i] = G[i^(1<<id)]|grap[id];
}
h[0] = 1; // no edge at all
for(int S=1; S<(1<<n); ++S){
int id = 0; // lowbit
for(; !(S>>id&1); ++id);
h[S] = h[S^(1<<id)];
int t = cnt[S&grap[id]];
for(int i=0; i<t; ++i)
h[S] = (h[S]<<1)%Mod;
}
f[1] = 1; // only it self
for(int S=3; S<(1<<n); S+=2){
for(int s=S; s; s=(s-1)&S){
int t = 1ll*f[s]*h[S^s]%Mod;
f[S] = (f[S]+Mod-t)%Mod;
}
f[S] = (f[S]+h[S])%Mod;
}
g[2] = 1; // only itself
for(int S=3; S<(1<<n); S=(S+1)|2){
for(int s=S; s; s=(s-1)&S){
int t = 1ll*g[s]*h[S^s]%Mod;
g[S] = (g[S]+Mod-t)%Mod;
}
g[S] = (g[S]+h[S])%Mod;
}
int ans = 1;
for(int i=0; i<m; ++i)
ans = (ans<<1)%Mod;
for(int S1=1; S1<(1<<n); S1+=2){
int all = ((1<<n)-1)^S1; // left
all ^= (all&G[S1]); // can't be reached
for(int S2=all; S2; S2=(S2-1)&all){
int t = 1ll*h[(1<<n)-1-S1-S2]*f[S1]%Mod;
ans = (ans+Mod-1ll*t*g[S2]%Mod)%Mod;
}
}
printf("%d\n",ans);
return 0;
}
吐槽
我的思路 = = = 容斥(考虑同时有多个 u u u 使得 1 , 2 1,2 1,2 能够同时到达) + + + 正难则反(何时 1 , 2 1,2 1,2 无路径)。
题解思路 = = = 正难则反 + + + 容斥。
果然我的思路还是太局限了……