把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4654】[CEOI2017] Mousetrap(DP+二分)

点此看题面

  • 给定一棵\(n\)个点的树,陷阱在房间\(A\),一只老鼠在房间\(B\)
  • 管理员每回合可以堵住一条边或者疏通一条边;老鼠每回合走一条边,它走过的边也会被堵住。
  • 求管理员至少操作几次才能把老鼠赶到房间\(A\)
  • \(n\le10^6\)

基本结论

首先,我们把陷阱所在的房间视作根节点。

显然,由于老鼠走过的边自己会堵上,那么管理员肯定有这样一个最优策略:

  • 在某一时刻把老鼠堵在某个地方。
  • 堵住从老鼠当前位置到根节点路径上每个节点的其他子节点。
  • 放出老鼠,然后它就只能一路跑到根节点结束。

然后老鼠也有一个最优策略:

  • 先向根节点走若干步(可以不走)。
  • 每次选择一个能苟活最久的子树溜进去,直至被管理员堵住。

知道这些我们就可以开始做这道题了。

动态规划预处理

我们先\(DP\)预处理出一个\(f_i\)表示老鼠进入以\(i\)为根的子树后,管理员至少需要几步才能把老鼠赶回\(i\)

显然,当老鼠在每某个节点时,管理员先操作,肯定会把\(f\)值最大的子树堵上,然后老鼠会跑到\(f\)值次大的子树中。

因此\(f_i\)就等于所有子节点\(f\)的次大值加上\(i\)的子节点个数(因为老鼠走的边需要疏通,老鼠没走的边需要堵住)。

二分答案

发现接下来直接做显然不太好做,主要是老鼠在向上走的过程中管理员可以同时堵住一些比较优的点。

于是我们二分答案,就可以在老鼠向上走的过程中判断出哪些子树是需要被堵上的,并判断一下步数够不够即可。

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,A,B,ee,lnk[N+5],s[N+5],f[N+5];struct edge {int to,nxt;}e[2*N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
int fa[N+5];I void dfs(CI x)//动态规划预处理
{
	RI Mx=0,Sx=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&
		(++s[fa[e[i].to]=x],dfs(e[i].to),f[e[i].to]>Mx?(Sx=Mx,Mx=f[e[i].to]):Sx=max(Sx,f[e[i].to]));
	f[x]=Sx+s[x];//次大值+子节点个数
}
int T,S[N+5],sum[N+5];I bool Check(RI k)//验证答案
{
	RI i,j,c=0,t;for(i=1;i^T;++i)//老鼠向上走
	{
		for(t=0,j=lnk[S[i]];j;j=e[j].nxt)
			e[j].to^S[i+1]&&e[j].to^S[i-1]&&sum[i]+f[e[j].to]>k&&++t;//统计需要删去的子节点个数
		if((c+=t)>i||(k-=t)<0) return 0;//如果当前步数不够了或是总步数不够了
	}return 1;
}
int main()
{
	RI i,x,y;for(F.read(n,A,B),i=1;i^n;++i) F.read(x,y),add(x,y),add(y,x);
	for(dfs(A),f[A]=0,i=B;i;i=fa[i]) S[++T]=i;for(i=T-1;i;--i) sum[i]=sum[i+1]+s[S[i]]-(i>1);//统计从一个点到根每个点有多少子节点
	RI l=0,r=n-1,mid;W(l^r) Check(mid=l+r>>1)?r=mid:l=mid+1;return printf("%d\n",r),0;//二分答案
}
posted @ 2020-12-03 07:19  TheLostWeak  阅读(78)  评论(0编辑  收藏  举报