【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}{\omega})\)。
代码有点赶。
#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;
}
标签:状态,懒狗,环上,int,UR,懒癌,反图,DP 来源: https://www.cnblogs.com/daklqw/p/13669163.html