【UR #6】懒癌

题目无限次看错……当然如果看对了还是做不出。注意一下只会开一枪,然后全局结束。

当然官方题解已经讲的很清楚了,所以还是特别推荐官方题解。


为了更好地理解,先从完全图入手(顺便拿一下部分分)。

假设有 \(K\) 个人,那么没懒狗的会看到 \(K\) 条懒狗,有懒狗的会看到 \(K - 1\) 条。

在第一天,如果没有枪声,说明没有人刚好看到 \(0\) 条,说明大家都至少看到 \(1\) 条。

在第二天,如果有人刚好看到了 \(1\) 条,由于没有 \(0\) 条,则他刚好是最小值 \(K - 1\),说明他有懒狗;如果没有枪声,说明没有人刚好看到 \(1\) 条。

……

归纳可证明所有有懒狗的人都在第 \(K\) 天枪毙懒狗。


再说一般图,相比于完全图,每个人多了一些看不到的狗。

但是,不变的是,每个人都在各自 DP,你预判到了我预判了你的预判,都是在一定的天数积累下得到自己的狗是懒狗。

也就是说,我们要设计一个 dp,得到一个阈值,在这个阈值之后找到懒狗。

那么:

  • 如果有人要枪毙狗,肯定是在之前没听到枪声。
  • 一个人在若干天之后,如果没听到枪声就会确定自己的狗是懒狗。
  • 对于确定的懒狗集合,其枪毙时间是最早有人确定自己的狗是懒狗的时间。
  • 对于一个人,他会枚举看不见的狗的所有情况,直到完全确定自己的狗是懒狗:
    • 即他会假设懒狗集合,使得自己的狗不在里面,看得见的狗状态不变,看不见的狗随便。
    • 当这个集合枪毙时间的最大值过了,还没有枪声,就与自己的狗不是懒狗矛盾,于是就会枪毙。

这样,我们枚举懒狗集合,然后根据最后一大条的原则进行转移,得到了一个 \(O(n4^n)\) 的做法。


但是我们在实现的过程中,发现了 DP 有环的情况,但是我们这个时候并不知道转移时是否每一个元素都有用,因此环的作用也不知道。

这个怎么解决呢?

先考虑特殊情况:啥时候会有环。因为看得到的狗是状态不会变的,变的是自己和自己没出边相连的狗。

关于这点,我们很难描述清楚我们的 DP。正难则反,我们交换一下条件,即:

  • 狗状态变的是自己有出边的,其他的不变。

很容易发现,这其实是在反图上操作。

基于这个操作和 DP 的定义,我们很容易给出 DP 有环的另一个定义:在反图上有环。

  • 因为我们在枚举反图环上的人时,会让环上下一个点的狗变成懒狗。这样一定会有环。

一个很显然的想法就是环上可以无限操作,但是如果操作环外的点呢?

  • 因为如果我们选择环外的人,因为是所有情况取 \(\max\),则它得到的答案不会小于 我们不去动环上点 的状态时的答案。
  • 如果选环上的人,则还可以保持环上有点。
  • 所以即使我们对每个人的时间取 \(\min\),答案还是不小于环上一直有懒狗时的答案。
  • 即答案正无穷大,即无解。

这说明了如果当前状态环上有懒狗,就整体无解。

同时,缩点成 DAG 后,在图上按拓扑序归纳证明:

  • 由于转移中有 \(\max\),对于能通过反图有向边到达环上的点,如果这个点上是懒狗,一定会有一个状态,使得环上有点。

这说明了,如果一个懒狗能通过反图有向边到环上,就整体无解。


在反图上,去掉环和能到达环的点,剩下的图是个 DAG。

再梳理一遍我们的改变 DP 状态时的操作:

  • 选择一个懒狗。
  • 将它变为好狗。
  • 将它出边连接的任意一些狗变成懒狗。

则我们很容易得到:

  • 在这个 DAG 上,DP 状态有偏序关系。

如果状态中没懒狗,是终止状态,定义其在第 \(0\) 天会有枪声。加入这个状态完美地契合了我们的转移。

那么显然容易归纳证明,只要存在一条懒狗,就不会在第 \(0\) 天有枪声。

同时因为 DP 的偏序关系,很容易得到,如果对于两个懒狗集合 \(S, T\)\(S \subset T\),则有 \(f(S) \leq f(T)\)

因为我们在选择懒狗后,改变状态后要使 DP 值最大,那么显然就是全变成懒狗最好了。

于是操作变成:

  • 选择一个懒狗。
  • 将它变为好狗。
  • 将它出边连接的狗全变成懒狗。

由于我们是选择所有懒狗的最小时间,那么如果当前状态中,懒狗 \(u\) 能到达懒狗 \(v (v \neq u)\),那么先选择懒狗 \(v\) 是不优的。

于是在每个状态中:

  • 每次我们只能选择懒狗 \(u\) 使得不存在懒狗 \(v (v \neq u)\) 能到达 \(u\)
  • 选择懒狗的次数和初始懒狗集合能到达的所有点的集合大小相等。
    • 即该懒狗集合枪声时间恰好等于能到达的点集合大小。
  • 选择枪毙的是在初始懒狗集合中的狗。
  • 选择枪毙的狗要使时间最小。
    • 即被枪毙的狗,是所有懒狗 \(u\) 使得不存在懒狗 \(v (v \neq u)\) 能到达 \(u\)

这样,我们得到了一个 \(O(2^n \mathrm{poly}(n))\) 的做法。


当然,都做到这一步了,还能不会多项式做法?

首先取出反图的 DAG,具体可以直接拓扑排序(选出度为 \(0\) 的点)。当然 tarjan 也可以。

即取出来的子图点数为 \(m\)

考虑到我们可以直接计算单个狗对答案的贡献。

对于第一问,对于一个狗 \(u\),我们得出有多少 \(v\) 能到达 \(u\)(包括 \(u\)),这样:

  • 只要能有点到达 \(u\) 即可。对于其他点,随便选。如果共 \(x\) 个点能到达 \(u\) (包括 \(u\))。
  • 答案为 \((2^x - 1)2^{m - x}\)

对于第二问,对于一个狗 \(u\),不能有懒狗 \(v\) 能到达它。

  • 记有 \(x\) 个点能到达它(包括 \(u\))。那么除了自己,其他 \(x - 1\) 个点都不能选。剩下的随便选。
  • 答案为 \(2^{m-x}\)

用一个传递闭包,然后直接统计答案即可。

时间复杂度 \(O(\frac{n^3}{w})\)


代码有点赶。

#include <bits/stdc++.h>

const int mod = 998244353;
typedef long long LL;
void reduce(int & x) { x += x >> 31 & mod; }
int mul(int a, int b) { return (LL) a * b % mod; }
int pow(int a, int b, int res = 1) {
	for (; b; b >>= 1, a = mul(a, a)) if (b & 1) res = mul(res, a);
	return res;
}
int remod(LL x) { x %= mod; return x + (x >> 63 & mod); }
const int MAXN = 3010;
typedef std::bitset<MAXN> B;
B to[MAXN];
int n;
int oud[MAXN];
int rk[MAXN], in[MAXN];
int main() {
	std::ios_base::sync_with_stdio(false), std::cin.tie(0);
	std::cin >> n;
	for (int i = 1; i <= n; ++i) {
		static char buf[MAXN];
		std::cin >> buf;
		for (int j = 1; j <= n; ++j)
			if (j != i && buf[j - 1] == '0')
				to[i].set(j), ++oud[i];
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j)
			if (!oud[j]) {
				oud[j] = -1;
				for (int k = 1; k <= n; ++k)
					if (to[k][j])
						--oud[k];
				in[rk[i] = j] = true;
				break;
			}
	}
	int m = std::accumulate(in + 1, in + 1 + n, 0);
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j < i; ++j)
			if (to[rk[i]].test(rk[j]))
				to[rk[i]] |= to[rk[j]];
	int ans1 = 0, ans2 = 0;
	for (int i = 1; i <= m; ++i) {
		int u = rk[i];
		int tc = 1;
		for (int j = i + 1; j <= m; ++j)
			tc += to[rk[j]].test(u);
		reduce(ans1 += mul(pow(2, tc) - 1, pow(2, m - tc)) - mod);
		reduce(ans2 += pow(2, m - tc) - mod);
	}
	std::cout << ans1 << ' ' << ans2 << std::endl;
	return 0;
}
posted @ 2020-09-14 20:26  daklqw  阅读(270)  评论(0编辑  收藏  举报