zoukankan      html  css  js  c++  java
  • 【洛谷4654】[CEOI2017] Mousetrap(DP+二分)

    点此看题面

    • 给定一棵(n)个点的树,陷阱在房间(A),一只老鼠在房间(B)
    • 管理员每回合可以堵住一条边或者疏通一条边;老鼠每回合走一条边,它走过的边也会被堵住。
    • 求管理员至少操作几次才能把老鼠赶到房间(A)
    • (nle10^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
    ",r),0;//二分答案
    }
    
  • 相关阅读:
    计算最大公因数
    最大子序列和问题
    C++三大函数:析构函数、复制构造函数和operator=
    C++函数返回值传递
    C++动态内存分配
    Halcon Assistants
    网格细分算法
    HDevelop Guide
    MeshLab
    point cloud registration
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4654.html
Copyright © 2011-2022 走看看