P1084 [NOIP2012 提高组] 疫情控制

打印 上一主题 下一主题

主题 901|帖子 901|积分 2703

思绪:

注意到答案有单调性,考虑二分答案。
如今由最优性问题转换为判定性问题。
我们很容易注意到一个性质:

  • 一个部队不停的往上跳是更优的。
  • 因为可以覆盖住更多的叶子节点。
那么对于二分答案的 \(mid\),我们只需要求出每一个部队在 \(mid\) 时间内能跳到的最高的那个点,然后判定一下是否覆盖了所有叶子节点即可。
但是这样会存在一个问题,即若当前这个点为 \(1\) 的儿子,且有多个部队跳到,则可能可以支援一下 \(1\) 号点的别的儿子(因为查抄点不能放在 \(1\) 上,则可能另有空余的时间经过 \(1\) 走到 \(1\) 的别的儿子)。
先来考虑一个简单的问题:

  • 若已知每个查抄点的位置,如何判断是否覆盖了所有叶子节点;设 \(g_i\) 表示 \(i\) 处是否有查抄点。
考虑先对于每个叶子节点标一个号(按照 dfn 序的大小),那么我们可以搜的时候预处理出 \(l_i,r_i\) 表示 \(i\) 这棵子树的叶子节点的编号为 \([l_i,r_i]\)。
则对于一个查抄点 \(x\),它可以覆盖 \([l_x,r_x]\) 这个范围,令这个区间都加 \(1\),最后检验一下是否每个叶子节点都被加了至少一次,可以用差分实现。
但是这样就无法考虑支援问题,因为我们需要知道 \(1\) 号节点的每一个儿子地点子树的叶子节点是否全部都被覆盖。
考虑树形 dp,令 \(dp_u\) 表示点 \(u\) 地点子树的叶子节点是否都被覆盖了,则状态转移方程为:

\[dp_u = \begin{cases} 1  & g_u = 1 \\ \operatorname{and}\limits_{(u,v) \in E} dp_v & g_u = 0 \end{cases}\]
若 \(1\) 号点的儿子 \(u\) 满意 \(dp_u=0\),那么该点需要支援,将所有需要支援的点按照到 \(1\) 的隔断排序。
则对于可以发动支援的点 \(v\),设 \(v\) 到达 \(1\) 后还可以走 \(k\) 的隔断(需要按 \(k\) 从小到大排序),那么我们可以贪心在需要支援的点会集找到隔断 \(\le k\) 且隔断最大的点,可以用 set 维护插入,删除,二分操作。
要注意一下细节,即若 \(v\) 不在 \(v\) 上设置查抄点,也可以使得 \(dp_v=1\),那么我们可以将 \(v\) 上的部队全部派出去支援;否则需要留一个部队守家,我们可以留剩下能走隔断最小的那个部队守。
此时还会有一个问题别问我为什么贪心有这么多细节,即若这个点上的某一部队到不了任何一个需要支援的点且不需要守家,但是可以到一个需要留一个部队守家的点,且那个守家的部队可以走足够的隔断去支援一个点,那么可以让这个点上的部队去取代它守家,这样会更优,称之为二次支援
考虑在一次支援完后记录一下所有在守家且可以支援的点和所有无法支援却不需要守家的点
那么二次支援一次支援差不多,对于无法支援却不需要守家的点的点 \(u\),设还可以走 \(k\) 的隔断,那么我们可以在所有在守家且可以支援的点中找到隔断 \(\le k\) 且隔断最大的点,也可以用 set 维护。
然后再来考虑跳跃的问题:

  • 如何求出一个点在 \(mid\) 时间内能上跳到的最高的点。
很容易想到二分跳的次数然后长链剖分求 \(k\) 级先人,这样是 \(\log^2 N\) 的,考虑优化。
考虑倍增与贪心,令 \(F_{i,j}\) 表示 \(i\) 的 \(2^j\) 级先人,\(W_{i,j}\) 表示 \(i\) 到它的 \(2^j\) 级先人的隔断,状态转移方程为:

\[F_{i,j} = F_{F_{i,j-1},j-1}\]

\[W_{i,j} = W_{i,j-1} + W_{F_{i,j-1},j-1}\]
那么可以直接贪心从高位到低位罗列二级制位 \(i\),若 \(W_{x,i} \le mid\),则令 \(mid \gets mid - W_{x,i}\),然后令 \(x\) 跳到 \(F_{x,i}\) 处;最后跳到不能再跳即可。
设 \(N,M\) 同阶,则总时间复杂度为 \(O(N \log N \log W)\)。
完备代码:

[code]#include#define Add(x,y) (x+y>=mod)?(x+y-mod)x+y)#define lowbit(x) x&(-x)#define pi pair#define pii pair#define iip pair#define ppii pair#define fi first#define se second#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x#define Full(a) memset(a,0,sizeof(a))#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);using namespace std;typedef double db;typedef unsigned long long ull;typedef long long ll;bool Begin;const ll N=5e4+10,M=16;inline ll read(){    ll x=0,f=1;    char c=getchar();    while(c'9'){        if(c=='-')          f=-1;        c=getchar();    }    while(c>='0'&&c
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大号在练葵花宝典

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表