【UR #6】懒癌

完全图的情况

第一天,所有人看一下有没有得懒癌的狗。如果没有,则自己的得了懒癌,开枪。

如果第一天没有人开枪,则至少有两只得懒癌的狗。第二天如果有人只看到一只得懒癌的狗,则自己的狗一定得了懒癌,开枪。

以此类推,如果第 \(k\) 天有人开枪,一定同时开 \(k\) 枪,总共有恰好 \(k\) 条得懒癌的狗。

状压DP

\(dp_U\) 表示懒癌狗的集合为 \(U\) 时开枪时间。

对于所有 \(x \in U\) 的人,它会考虑,如果 \(x\) 狗没有得懒癌,设集合 \(V\) 为满足以下条件的集合:

  • $ x \notin V$
  • \(x\) 可以看出 \(y\) 有没有得懒癌,则 \(y \in V\) 当且仅当 \(y\) 得懒癌
  • \(x\) 看不出 \(y\) 有没有得懒癌, \(y\) 可以在 \(V\) 中也可以不在 \(V\)

那么他应当在 \(\max\{ dp_V\}\) 天之前听到枪声。否则,他会在 \(\max\{DP_V\} + 1\) 天枪杀自己的狗。

因此 \(DP_U = \min_x\{ \max\{dp_V + 1\}\}\)

需要注意的时,转移可能会成环,环中的状态永远不会开枪。

算多少只狗被杀死只需要求有多少个 \(x\) 满足 \(\max\{DP_V\} + 1 = DP_U\) 即可。

这样的复杂度是 \(O(4^n n)\)

事实上,一个人可以认为自己看不出是不是懒癌的狗得了懒癌。这样转移就只需要枚举一个集合 \(V\),复杂度优化到 \(O(2^nn)\)

正解

考虑建一张新图:如果 \(u\) 不知道 \(v\) 有没有得懒癌,由 \(u\) 指一边向 \(v\)

在这张新图上考虑上面的那个 dp。计算 \(DP_U\) 的时候,我们将所有 \(x \in U\) 的节点染黑,所有可以转移到 \(U\) 的集合 \(V\) 是将 \(U\) 中一个点染白,然后染黑其出边所得到的黑点集合。

如果 \(U\) 中的点能到达环, \(DP_U = +\infty\)

所以所有能到达环的节点必须不得懒癌。把这些点删掉,得到一张 \(DAG\)

我们发现 \(DP_U\) 的值就可以直接计算了。因为那个转移方程就相当于,每次可以选择将一个黑点染白,然后染黑其所有出边,直到所有点都为白色所需的最小步数,这显然是 \(U\) 的后继节点个数。

而死的狗的数目,就是要使染色步数最小,第一次染色可以选择的黑点数目,这就是没有前驱黑点的黑点数目。

只需要用 bitset 算出每个点可以被多少个点到达即可。

代码实现上,拓扑排序可以用点的入度来做,这样就不会算到能到达环的点了。

总复杂度 \(O(\frac{n^3}{\omega})\)

#pragma GCC optimize("2,Ofast,inline")
#include<bits/stdc++.h>
using namespace std;
const int N = 3e3 + 10;
const int mod = 998244353;

inline void upd(int &x, int y) {
	(x += y) >= mod ? x -= mod : 0;
}

int n;
int deg[N], pw[N], a[N][N];
int tot, q[N];
char s[N];
bitset<N> dp[N];

int main() {
	pw[0] = 1;
	for (int i = 1; i < N; ++i) pw[i] = pw[i - 1] * 2 % mod;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		scanf("%s", s + 1);
		for (int j = 1; j <= n; ++j) {
			if (i == j) continue;
			a[i][j] = (s[j] == '1');
			if (!a[i][j]) ++deg[i];
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (deg[i] == 0) q[++tot] = i;
	}
	for (int i = 1; i <= tot; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (q[i] != j && !a[j][q[i]] && --deg[j] == 0) {
				q[++tot] = j;
			}
		}
	}
	for (int i = 1; i <= tot; ++i) {
		dp[q[i]][q[i]] = 1;
	}
	for (int i = tot; i >= 1; --i) {
		for (int j = 1; j <= tot; ++j) {
			if (i != j && !a[q[i]][q[j]]) dp[q[j]] |= dp[q[i]];
		}
	}
	int ans1 = 0, ans2 = 0;
	for (int i = 1; i <= tot; ++i) {
		int t = dp[q[i]].count();
		upd(ans1, 1LL * (pw[t] - 1) * pw[tot - t] % mod);
		upd(ans2, pw[tot - t]);
	}
	printf("%d %d\n", ans1, ans2);
	return 0;
}
posted @ 2019-11-04 18:54  Vexoben  阅读(154)  评论(0编辑  收藏  举报