Address
https://cf17-relay-open.contest.atcoder.jp/tasks/relay2_i
Meaning
一个 ( )个点 ( )的有向图,但每条边的方向未定。现在需要给每一条边定向,使得存在一个点 ,从 出发能到达 ,从 出发也能到达 。求有多少种不同方案。两种方案不同当且仅当这两种方案中存在一条边的方向不同。
Solution
看到
的数据,容易想到状压。
先预处理出:
表示两端都属于点集
的边的数量。
表示至少一端属于点集
的边的数量。
状态
表示对两端都属于点集
的边进行定向,使从
出发能到达
中所有点的方案数。
凭感觉,这和无向连通图计数差不多。
边界当然是
。
转移:
(1)对所有两端都属于点集
的边进行定向,方案数
。
(2)容斥。对于每个
,求出从
能且仅能走到点集
的方案数,从
中去除掉。
①对点集
之间的边进行定向,方案数
。
②不能存在一条边,出发点在
,到达点在
。
③对点集
之间的边随意定向,方案数
。
所以,和无向连通图计数类似:
最后来统计答案。
还是容斥。用 减去不合法的方案。
考虑 能到达的点集 和 能到达的点集 , 和 的交集一定为空。
这相当于 。可用枚举子集实现。
还需要保证 。
如何理解第三个条件:如果存在一条边直接跨越了 和 ,那么 和 一定能到达同一个点(这条跨越的边的端点之一)。而如果有边横跨 和 ,那么 不仅包含了 和 ,还包含了横跨 和 的边,于是就一定有 。所以必须保证 。
除了至少有一个端点在 或在 内的边,其他的 条边都能随便定向。
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;
}