zoukankan      html  css  js  c++  java
  • 【UR #6】懒癌

    完全图的情况

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

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

    以此类推,如果第 (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;
    }
    
  • 相关阅读:
    C++ 日期 & 时间
    C++ 引用
    C++ 指针
    C++ 字符串
    C++ 数组
    C++ 数字
    C++ 函数
    C++ 判断
    C++ 循环
    C++ 运算符
  • 原文地址:https://www.cnblogs.com/Vexoben/p/11794003.html
Copyright © 2011-2022 走看看