【AGC016F】Games on DAG(SG函数,状压DP,子集枚举)

15 篇文章 0 订阅
10 篇文章 0 订阅

Description

有一个DAG,1号点和2号点各有一个石子.两个玩家交替将其中一个石子沿着一条有向边移动,不能移动的玩家输掉游戏.问有多少种选边的方案使得先手必胜.


Solution

将题目的条件转化成 1 1 号点和2号点的SG函数不相等.
我们设 fS f S 表示对于点集 S S 有多少种选边方案使得1号点和2号点 SG S G 函数相等.( S S 要么同时包含1,2,要么同时不包含 1,2 1 , 2 )
对于当前枚举的点集 A A ,我们枚举它的子集B以及 C=AB C = ∁ A B , B B 表示SG函数为0的点集, C C 表示SG函数非0的点集.
我们考虑怎么连边:
1. B B 内部不能连边(一旦连边,SG函数就不等于0了)
2. C C 内部连边方案数为fC,即在原来点集 C C 的基础上,每一个点的SG函数都加 1 1
3. BC:乱连,不会对SG函数值造成影响
4. CB C → B : C C 中的每一个点至少向一个B中的点连边

然后通过子集枚举来转移即可.


Code

/************************************************
 * Au: Hany01
 * Date: Aug 27th, 2018
 * Prob: [AGC016F] Games on DAG
 * Email: hany01dxx@gmail.com & hany01@foxmail.com
 * Inst: Yali High School
************************************************/

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef long double LD;
typedef pair<int, int> PII;
#define rep(i, j) for (register int i = 0, i##_end_ = (j); i < i##_end_; ++ i)
#define For(i, j, k) for (register int i = (j), i##_end_ = (k); i <= i##_end_; ++ i)
#define Fordown(i, j, k) for (register int i = (j), i##_end_ = (k); i >= i##_end_; -- i)
#define Set(a, b) memset(a, b, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define x first
#define y second
#define pb(a) push_back(a)
#define mp(a, b) make_pair(a, b)
#define SZ(a) ((int)(a).size())
#define ALL(a) a.begin(), a.end()
#define INF (0x3f3f3f3f)
#define INF1 (2139062143)
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define count __builtin_popcount
#define y1 wozenmezhemecaia

template <typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }

inline int read() {
    static int _, __; static char c_;
    for (_ = 0, __ = 1, c_ = getchar(); c_ < '0' || c_ > '9'; c_ = getchar()) if (c_ == '-') __ = -1;
    for ( ; c_ >= '0' && c_ <= '9'; c_ = getchar()) _ = (_ << 1) + (_ << 3) + (c_ ^ 48);
    return _ * __;
}

const int maxn = 15, maxs = 1 << 15, MOD = 1e9 + 7;

int n, m, To[maxn], f[maxs];

inline int ad(int x, int y) { if ((x += y) >= MOD) return x - MOD; return x; }
#define chk(x) (!((x) & 3) || ((x) & 3) == 3)

int main()
{
#ifdef hany01
    freopen("agc016f.in", "r", stdin);
    freopen("agc016f.out", "w", stdout);
#endif

    static int uu, vv;

    n = read(), m = read();
    For(i, 1, m) uu = read() - 1, vv = read() - 1, To[uu] |= (1 << vv);

    f[0] = 1;
    For(A, 1, (1 << n) - 1) if (chk(A))
        for (int B = A, res, C; B; (-- B) &= A) if (chk(B)) {
            res = f[C = A ^ B];
            rep(i, n)
                if (B >> i & 1) res = (LL)res * (1 << count(To[i] & C)) % MOD;
                else if (C >> i & 1) res = (LL)res * ((1 << count(To[i] & B)) - 1) % MOD;
            f[A] = ad(f[A], res);
        }
    printf("%d\n", ad((1ll << (m >> 1)) % MOD * ((1ll << (m - (m >> 1))) % MOD) % MOD, MOD - f[(1 << n) - 1]));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值