[Atcoder Code Festival 2017 Team Relay I]Nice to Meet You(状压dp+容斥)

Address

https://cf17-relay-open.contest.atcoder.jp/tasks/relay2_i

Meaning

一个 N 2 N 15 )个点 M 1 M N ( N 1 ) / 2 )的有向图,但每条边的方向未定。现在需要给每一条边定向,使得存在一个点 u ,从 1 出发能到达 u ,从 2 出发也能到达 u 。求有多少种不同方案。两种方案不同当且仅当这两种方案中存在一条边的方向不同。

Solution

看到 N 15 的数据,容易想到状压。
先预处理出:
c [ S ] 表示两端都属于点集 S 的边的数量。
d [ S ] 表示至少一端属于点集 S 的边的数量。
状态 f [ u = 1 / 2 ] [ S ] ( u S ) 表示对两端都属于点集 S 的边进行定向,使从 u 出发能到达 S 中所有点的方案数。
凭感觉,这和无向连通图计数差不多。
边界当然是 f [ u ] [ { u } ] = 1
转移:
(1)对所有两端都属于点集 S 的边进行定向,方案数 2 c [ S ]
(2)容斥。对于每个 T S , u T , T S ,求出从 u 能且仅能走到点集 T 的方案数,从 2 c [ S ] 中去除掉。
①对点集 T 之间的边进行定向,方案数 f [ u ] [ T ]
②不能存在一条边,出发点在 S T ,到达点在 T
③对点集 S T 之间的边随意定向,方案数 2 c [ S T ]
所以,和无向连通图计数类似:

f [ u ] [ S ] = 2 c [ S ] T S , u T , T S 2 c [ S T ] f [ u ] [ T ]

最后来统计答案。
还是容斥。用 2 M 减去不合法的方案。
考虑 1 能到达的点集 S 2 能到达的点集 T S T 的交集一定为空。
这相当于 T ( { 1 , 2 , . . . , N } S ) 。可用枚举子集实现。
还需要保证 1 S , 2 T , c [ S ] + c [ T ] = c [ S T ]
如何理解第三个条件:如果存在一条边直接跨越了 S T ,那么 1 2 一定能到达同一个点(这条跨越的边的端点之一)。而如果有边横跨 S T ,那么 c [ S T ] 不仅包含了 c [ S ] c [ T ] ,还包含了横跨 S T 的边,于是就一定有 c [ S ] + c [ T ] < c [ S T ] 。所以必须保证 c [ S ] + c [ T ] = c [ S T ]
除了至少有一个端点在 S 或在 T 内的边,其他的 m d [ i j ] 条边都能随便定向。
a n s w e r = 2 M 1 S 2 T , S T = Ø , c [ S ] + c [ T ] = c [ S T ] 2 m d [ i j ] f [ 1 ] [ S ] f [ 2 ] [ T ]

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;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/81047593