完全图的情况
第一天,所有人看一下有没有得懒癌的狗。如果没有,则自己的得了懒癌,开枪。
如果第一天没有人开枪,则至少有两只得懒癌的狗。第二天如果有人只看到一只得懒癌的狗,则自己的狗一定得了懒癌,开枪。
以此类推,如果第 (k) 天有人开枪,一定同时开 (k) 枪,总共有恰好 (k) 条得懒癌的狗。
状压DP
设 (dp_U) 表示懒癌狗的集合为 (U) 时开枪时间。
对于所有 (x in U) 的人,它会考虑,如果 (x) 狗没有得懒癌,设集合 (V) 为满足以下条件的集合:
- $ x otin 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
", ans1, ans2);
return 0;
}