题目链接:[AGC016F] Games on DAG

题意:

给定 $n$ 个点 $m$ 条边的 $\text{DAG}$,现在 $1,2$ 号点上分别有一个棋子。$\text{Alice}$ 和 $\text{Bob}$ 轮流选择一个棋子,将其向一条出边移动。不能移动者败,问 $2^m$ 张生成子图中,有多少先手必胜。
$2\le n\le 15$,$1\le m\le {(n-1)\times n\over 2}$,$\text{5s}$。

根据博弈论结论,先手必胜当且仅当初始局面 $\text{SG}\ne 0$。而两颗棋子独立,所以局面 $\text{SG}$ 为 $\text{SG(1)}\text{ xor }\text{SG(2)}$。转变为计数先手必败的情况,则 $\text{SG(1)}=\text{SG(2)}$。

考虑到 $n$ 很小,所以节点的 $\text{SG}$ 值不会很大。因此考虑枚举 $\text{SG}$ 值为一个固定值 $k$ 的集合,计算使当前划分方案成立的 $\text{DAG}$ 的个数。

从小到大确定 $\text{SG}$ 的集合不太好做,因为需要做的是形如:“在每一个 $\text{SG}<k$ 的集合中选择一个元素与当前点连边”,这样就与每一个集合的具体大小相关了。

但从大到小不一样,$\text{SG}>k$ 的所有元素对当前集合都是一样的——都可以随便连过去。

因此设 $S$ 是 $\text{SG}>k$ 的集合,$T$ 是 $\text{SG}=k$ 的集合。则 $1,2$ 同时在 $T$ 或同时不在 $T$。

  • 对于 $S\rightarrow T$ 的边,每一个 $S$ 中的元素都至少要向 $T$ 中连一条边。
  • 对于 $T\rightarrow S$ 的边,随便连都可以。
  • 对于 $T$ 内部的边,一条都不能连。

因此就得到了一个 $\mathcal{O}(n\times 3^n)$ 的算法。

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 18;
const int MAXS = (1 << 17) + 10;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;

int n, m;
int cnt[MAXN][MAXS], p2[MAXN * MAXN];
int link[MAXN][MAXN];
int dp[MAXS];

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

bool chk(int T) {
    if((T & 1) + ((T >> 1) & 1) == 1) return false;
    return true;
}

signed main () {
#ifdef FILE
    freopen(".in", "r", stdin);
    freopen(".out", "w", stdout);
#endif
    read(n), read(m); p2[0] = 1;
    for(int i = 1; i <= m; ++i) p2[i] = p2[i - 1] * 2 % mod;
    for(int i = 1, x, y; i <= m; ++i) {
        read(x), read(y);
        link[x][y]++;
    }
    for(int S = 0; S < (1 << n); ++S)
        for(int i = 1; i <= n; ++i) {
            cnt[i][S] = cnt[i][S ^ lowbit(S)];
            if(S) cnt[i][S] += link[i][(int)log2(lowbit(S)) + 1];
        }
    dp[0] = 1;
    for(int S = 1; S < (1 << n); ++S) {
        for(int T = S, K, coe; T; T = (T - 1) & S) {
            K = S ^ T, coe = 1; if(!chk(T)) continue;
            for(int i = 1; i <= n; ++i) {
                if((T >> (i - 1)) & 1) coe = coe * p2[cnt[i][K]] % mod;
                if((K >> (i - 1)) & 1) coe = coe * (p2[cnt[i][T]] - 1) % mod;
            }
            (dp[S] += dp[K] * coe) %= mod;
        }
    }
    printf("%lld\n", (p2[m] - dp[(1 << n) - 1] + mod) % mod);
    return 0;
}

标签: 计数, 博弈论

仅有一条评论

  1. bigmurmur bigmurmur

    哇哇哇汪汪汪汪汪我!

添加新评论