• 【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 eq u)),那么先选择懒狗 (v) 是不优的。

    于是在每个状态中:

    • 每次我们只能选择懒狗 (u) 使得不存在懒狗 (v (v eq u)) 能到达 (u)
    • 选择懒狗的次数和初始懒狗集合能到达的所有点的集合大小相等。
      • 即该懒狗集合枪声时间恰好等于能到达的点集合大小。
    • 选择枪毙的是在初始懒狗集合中的狗。
    • 选择枪毙的狗要使时间最小。
      • 即被枪毙的狗,是所有懒狗 (u) 使得不存在懒狗 (v (v eq 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;
    }
    
  • 相关阅读:
    ubuntu 13.04 root权限设置方法详解
    观锁和乐观锁——《POJOs in Action》
    观锁与悲观锁(Hibernate)
    关于python的环境变量问题
    vs2010 调试快捷键
    VIM7.3中文手册
    Java最全文件操作实例汇总
    response letter模板
    数据库字段类型
    Tomcat系列之Java技术详解
  • 原文地址:https://www.cnblogs.com/daklqw/p/13669163.html
Copyright © 2020-2023  润新知