AGC 016 F - Games on DAG(状压dp)

题意

给你一个有 \(n\) 个点 \(m\) 条边 DAG 图,点的标号和拓扑序一致。

现在有两个人进行博弈,有两个棋子分别在 \(1, 2\) 号点上,需要不断移动到它指向的点上。

如果当前两个点都无法移动,那么就视为当前操作的人失败。

问有多少边集满足先手必胜。

\(\displaystyle 2 \le n \le 15, m \le \frac{n \times (n+1)}{2}\)

题解

参考了 wxh010910 大佬的博客

首先利用博弈的 SG 函数易得,如果 \(1\) 号点和 \(2\) 号点的 SG 值异或不为 \(0\) 则先手必败。

但这样不好计数,我们考虑它的反面,求 \(1,2\) 号点 SG 值异或为 \(0\) 的方案数。

也就是 \(1,2\) 号点 SG 值为 \(0\) 的方案数。

考虑边集显然是不现实的,改成考虑点集,令 \(f(S)\)\(S\) 这个集合满足 \(SG(1) = SG(2)\) 的方案数。

我们考虑枚举它的一个子集 \(T\) 假设满足点集中所有的点 \(SG\) 都不为 \(0\) ,并且它对于 \(S\) 的补集 \(U\) 都满足 \(SG\) 都为 \(0\)

这是连边后的情况,连边前 \(T\) 集合会存在 \(SG\)\(0\) 的点。

我们考虑这些点的连边方案数。

  1. \(U\) 集合内部,显然不能连边。(不然不能保证 SG 为 \(0\)
  2. \(U \to T\) 随意连边。
  3. \(T \to U\) 要保证 \(T\) 中每个点都需要有一条向 \(U\) 连的边(要保证 \(T\) 集合中的 SG 不为 \(0\)
  4. \(T\) 内部的方案数,正好就是 \(f(T)\) 。这是为什么呢?把 \(T\) 中每个点都新连向一个 \(SG = 0\) 的点,那么它的 \(SG\) 都会增加 \(1\)

然后为了保证 \(SG(1)=SG(2)\) 枚举 \(S, T, U\) 的时候需要保证 \(1, 2\) 同时选或同时不选。

为什么这样是对的呢?

因为我们保证了 \(T\) 集合的方案本身满足 \(SG(1) = SG(2)\) 那么之后同时 \(+1\) 也会满足这个性质。

\(U\) 集合显然也是成立的( \(SG(1) = SG(2) = 0\) )。


然后枚举的时候有细节,我们考虑枚举 \(U\)\(S\) 的子集,因为 \(U\) 必然不为 \(\varnothing\) ,而 \(T\) 可以为 \(\varnothing\)

总结

对于一类关于边集的状压 \(dp\) ,可以考虑转化成点集,然后计算连边的方案数。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBTG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

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

inline int read() {
	int x = 0, fh = 1; char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
	for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
	return x * fh;
}

void File() {
#ifdef zjp_shadow
	freopen ("F.in", "r", stdin);
	freopen ("F.out", "w", stdout);
#endif
}

const int N = 15, Mod = 1e9 + 7;

int n, m, G[N], Table[1 << N], dp[1 << N];

int main () {

	File();

	n = read(); m = read();
	For (i, 1, m) { int u = read() - 1, v = read() - 1; G[u] |= (1 << v); }

	int maxsta = (1 << n) - 1; dp[0] = 1;
	For (i, 0, maxsta) Table[i] = 1 << __builtin_popcount(i);

	For (S, 2, maxsta) if ((S & 1) == ((S >> 1) & 1))
		for (int U = S; U; U = (U - 1) & S) if ((U & 1) == ((U >> 1) & 1)) {
			int Ways = 1, T = S ^ U;
			For (p, 0, n - 1) if ((S >> p) & 1) {
				if ((U >> p) & 1) Ways = 1ll * Table[G[p] & T] * Ways % Mod;
				else Ways = 1ll * (Table[G[p] & U] - 1) * Ways % Mod;
			}
			dp[S] = (dp[S] + 1ll * Ways * dp[T]) % Mod;
		}

	int ans = 1; For (i, 1, m) ans = (ans << 1) % Mod;
	printf ("%d\n", (ans + Mod - dp[maxsta]) % Mod);

	return 0;
}
posted @ 2018-08-16 20:56  zjp_shadow  阅读(523)  评论(0编辑  收藏  举报