Luogu P4654 [CEOI2017]Mousetrap

Link
我们以有陷阱的房子为根。
一旦老鼠向下进入一个子树内,且管理员不帮它清理上一次走过的道路,那么它最终会被自己弄脏的走廊困在某个叶子节点。
老鼠被困在叶子节点之后管理员的最优决策一定是先把这个叶子节点到根节点路径上的岔路口全部堵上,然后一次次把老鼠当前位置到父亲的边清理直到老鼠走到根。
因此在老鼠向下走时管理员帮它清理上一次走过的道路一定是不优的。管理员本可以在这期间进行堵上岔路口的操作。
那么现在情况就是老鼠先往上走一段距离,然后走进一棵子树。
走进任意一棵子树之后的操作步数是可以dp出来的,设\(f_u\)表示老鼠进入了\(u\)的子树,然后又被赶回\(u\)的最小步数。
在老鼠向下走之后管理员每次堵住当前点通向\(f\)值最大的子树的边一定最优。
因此转移方程为\(f_u=\operatorname{2ndmax}\limits_{v\in son_u}(f_v)+deg_u-1\)
然后我们再处理出\(sum_u\)表示\(u\)节点到根路径上的岔路口数。
那么老鼠第一次向下走,走到\(u\),然后游戏结束的最少步数为\(g_u=f_u+sum_{fa_u}-[fa_u\ne start]\)
那么现在问题就是老鼠第一次向下走会走到哪个点。
二分答案\(lim\)变成判定性问答题,老鼠肯定是想找个\(g_u>lim\)的点走下去,而管理员就需要把这样的点封上。因为管理员在操作\(lim\)也会变小。
最后如果能够全部堵上那么\(lim\)时间就是合法的,否则不合法。

#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
const int N=1000007;
char ibuf[1<<24],*iS=ibuf;int n,root,start,cnt,fa[N],f[N],id[N],sum[N];std::vector<int>e[N],g[N];
int read(){int x=0;while(isspace(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void dfs(int u)
{
    int mx1=0,mx2=0;
    for(int v:e[u])
    {
	if(v==fa[u]) continue;
	fa[v]=u,dfs(v);
	if(f[v]>mx1) mx2=mx1,mx1=f[v]; else if(f[v]>mx2) mx2=f[v];
    }
    f[u]=mx2+(int)e[u].size()-1;
}
int check(int lim)
{
    for(int i=cnt,res=0;i>1;--i)
    {
	++res;int now=0;
	for(int v:g[id[i]])
	    if(v-now>lim)
	    {
		if(!res||!lim)return 0;
		--lim,--res,++now;
	    }
    }
    return 1;
}
int find()
{
    int l=0,r=n,mid;
    while(l<=r) mid=(l+r)/2,check(mid)? r=mid-1:l=mid+1;
    return l;
}
int main()
{
    fread(ibuf,1,1<<24,stdin);
    n=read(),root=read(),start=read(),e[root].push_back(0);
    for(int i=1,u,v;i<n;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
    dfs(root);
    for(int u=start;u;u=fa[u]) id[++cnt]=u;
    std::reverse(id+1,id+cnt+1);
    for(int i=1;i<=cnt;++i) sum[i]=sum[i-1]+(int)e[id[i]].size()-2+(i==cnt);
    for(int i=cnt,u;u=id[i],i;--i) for(int v:e[u]) if(v!=id[i-1]&&v!=id[i+1]) g[u].push_back(f[v]+sum[i]);
    printf("%d",find());
}
posted @ 2020-06-01 11:31  Shiina_Mashiro  阅读(172)  评论(0编辑  收藏  举报