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

Solution

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

f[u][S]=2c[S]TS,uT,TS2c[ST]f[u][T] f [ u ] [ S ] = 2 c [ S ] − ∑ T ⊂ S , u ∈ T , T ≠ S 2 c [ S − T ] f [ u ] [ T ]

最后来统计答案。
还是容斥。用 2M 2 M 减去不合法的方案。
考虑 1 1 能到达的点集 S 2 2 能到达的点集 T S S T 的交集一定为空。
这相当于 T({1,2,...,N}S) T ∈ ( { 1 , 2 , . . . , N } − S ) 。可用枚举子集实现。
还需要保证 1S,2T,c[S]+c[T]=c[ST] 1 ∈ S , 2 ∈ T , c [ S ] + c [ T ] = c [ S ∪ T ]
如何理解第三个条件:如果存在一条边直接跨越了 S S T ,那么 1 1 2 一定能到达同一个点(这条跨越的边的端点之一)。而如果有边横跨 S S T ,那么 c[ST] c [ S ∪ T ] 不仅包含了 c[S] c [ S ] c[T] c [ T ] ,还包含了横跨 S S T 的边,于是就一定有 c[S]+c[T]<c[ST] c [ S ] + c [ T ] < c [ S ∩ T ] 。所以必须保证 c[S]+c[T]=c[ST] c [ S ] + c [ T ] = c [ S ∩ T ]
除了至少有一个端点在 S S 或在 T 内的边,其他的 md[ij] m − d [ i ∩ j ] 条边都能随便定向。
answer=2M1S2T,ST=Ø,c[S]+c[T]=c[ST]2md[ij]f[1][S]f[2][T] 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值