Address
https://cf17-relay-open.contest.atcoder.jp/tasks/relay2_i
Meaning
一个 N N ( )个点 M M ( )的有向图,但每条边的方向未定。现在需要给每一条边定向,使得存在一个点 u u ,从 出发能到达 u u ,从 出发也能到达 u u 。求有多少种不同方案。两种方案不同当且仅当这两种方案中存在一条边的方向不同。
Solution
看到 的数据,容易想到状压。
先预处理出:
c[S]
c
[
S
]
表示两端都属于点集
S
S
的边的数量。
表示至少一端属于点集
S
S
的边的数量。
状态 表示对两端都属于点集
S
S
的边进行定向,使从 出发能到达
S
S
中所有点的方案数。
凭感觉,这和无向连通图计数差不多。
边界当然是 。
转移:
(1)对所有两端都属于点集
S
S
的边进行定向,方案数 。
(2)容斥。对于每个
T⊂S,u∈T,T≠S
T
⊂
S
,
u
∈
T
,
T
≠
S
,求出从
u
u
能且仅能走到点集 的方案数,从
2c[S]
2
c
[
S
]
中去除掉。
①对点集
T
T
之间的边进行定向,方案数 。
②不能存在一条边,出发点在
S−T
S
−
T
,到达点在
T
T
。
③对点集 之间的边随意定向,方案数
2c[S−T]
2
c
[
S
−
T
]
。
所以,和无向连通图计数类似:
最后来统计答案。
还是容斥。用 2M 2 M 减去不合法的方案。
考虑 1 1 能到达的点集 和 2 2 能到达的点集 , S S 和 的交集一定为空。
这相当于 T∈({1,2,...,N}−S) T ∈ ( { 1 , 2 , . . . , N } − S ) 。可用枚举子集实现。
还需要保证 1∈S,2∈T,c[S]+c[T]=c[S∪T] 1 ∈ S , 2 ∈ T , c [ S ] + c [ T ] = c [ S ∪ T ] 。
如何理解第三个条件:如果存在一条边直接跨越了 S S 和 ,那么 1 1 和 一定能到达同一个点(这条跨越的边的端点之一)。而如果有边横跨 S S 和 ,那么 c[S∪T] c [ S ∪ T ] 不仅包含了 c[S] c [ S ] 和 c[T] c [ T ] ,还包含了横跨 S S 和 的边,于是就一定有 c[S]+c[T]<c[S∩T] c [ S ] + c [ T ] < c [ S ∩ T ] 。所以必须保证 c[S]+c[T]=c[S∩T] c [ S ] + c [ T ] = c [ S ∩ T ] 。
除了至少有一个端点在 S S 或在 内的边,其他的 m−d[i∩j] m − d [ i ∩ j ] 条边都能随便定向。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Tubtet(i, k) for (k = (i - 1) & i; k; k = (k - 1) & i)
#define Subset(i, k) for (k = i; k; k = (k - 1) & i)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 17, M = 405, C = (1 << 15) + 5, PYZ = 1e9 + 7;
int n, m, g[N][N], f[3][C], c[C], d[C], Cm, p[M], ans;
int main() {
int i, j, k, x, y; n = read(); m = read(); p[0] = 1;
For (i, 1, m) x = read(), y = read(), g[x][y]++, p[i] = 2ll * p[i - 1] % PYZ;
Cm = (1 << n) - 1; For (i, 0, Cm) For (j, 1, n) For (k, 1, n) if (g[j][k]) {
if (((i >> j - 1) & 1) && ((i >> k - 1) & 1)) c[i] += g[j][k];
if (((i >> j - 1) & 1) || ((i >> k - 1) & 1)) d[i] += g[j][k];
}
For (k, 1, 2) For (i, 0, Cm) {
if (!((i >> k - 1) & 1)) continue; f[k][i] = p[c[i]]; Tubtet(i, j) {
if (!((j >> k - 1) & 1)) continue;
f[k][i] = (f[k][i] - 1ll * f[k][j] * p[c[i - j]] % PYZ + PYZ) % PYZ;
}
}
ans = p[m]; For (i, 0, Cm) {
if (!(i & 1) || ((i >> 1) & 1)) continue; Subset(Cm - i, j) {
if (!((j >> 1) & 1) || c[i] + c[j] < c[i + j]) continue;
ans = (ans - 1ll * f[1][i] * f[2][j] % PYZ *
p[m - d[i + j]] % PYZ + PYZ) % PYZ;
}
}
cout << ans << endl; return 0;
}