「解题报告」[luoguP6584]重拳出击 (贪心).md

「解题报告」[luoguP6584]重拳出击 (贪心).md

传送门

题面

题意

有一颗大小为 (n) 的边权为 (1) 的无根树, 树上有 (m) 个 Youyou.

初始时, 小 Z 在 (x) 号节点上.

每一回合, 小 Z 可以杀死所有与它距离小于等于 (k) 的 Youyou, 并可以移动到相邻节点或保持不动.

回合结束时, 所有的 Youyou 会向小 Z 的方向移动一条边, 若它与小 Z 在同一节点上, 则不移动.

求小 Z 消灭所有的 Youyou 所需的最小回合数.

数据范围

(1 le n,m le 4 imes 10^5, 0 le k le 10^6).


思路

以小 Z 初始位置 x 为根节点

找出深度最深的 Youyou, 记为 y, 记它的深度为 dis. 可以证明 : 处理完 y 后再回到根节点, 所需的天数为 dis. (对 dis 分奇偶讨论一下就能证).

而 dis 天后, 剩余的 Youyou 肯定会与小 Z 相遇. (否则它的深度就大于 dis.)

所以先处理 y 后再处理剩下的 Youyou 可能不会比在根节点等着更劣.

具体做法 : 遍历从 x 到 y 的这条链, 对每个节点算出 "处理完 y 后再去处理它子树中深度次大的点" 的时间, 取个 max, 最后答案即为 "处理 y 所需的时间" +max.


代码

#include<bits/stdc++.h>
using namespace std;
const int _=4e5+7,__=8e5+7,inf=0x3f3f3f3f;
int n,m,X,K,ans,maxn,dis[_],pl;
int lst[_],nxt[__],to[__],tot;
bool b[_],tag;
void _add(int x,int y){
    nxt[++tot]=lst[x]; to[tot]=y; lst[x]=tot;
    nxt[++tot]=lst[y]; to[tot]=x; lst[y]=tot;
}
void _init(){
    cin>>n; int x,y;
    for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		_add(x,y);
    }
    cin>>m;
    for(int i=1;i<=m;i++){
		scanf("%d",&x);
		b[x]=1;
    }
    cin>>K>>X;
}
void _pre(int u,int fa){
    dis[u]= b[u] ?0 :-inf;
    for(int i=lst[u];i;i=nxt[i]){
		int v=to[i];
		if(v==fa) continue;
		_pre(v,u);
		dis[u]=max(dis[u],dis[v]+1);
    }
}
void _dfs(int u,int fa,int dep){
    int x1=0,x2=0;
    for(int i=lst[u];i;i=nxt[i]){
		int v=to[i];
		if(v==fa) continue;
		if(dis[v]>dis[x1]){ x2=x1; x1=v; }
		else if(dis[v]>dis[x2]) x2=v;
    }
    if(x2){
		int stp=pl-dep,pth;
		if(tag) pth=max(0,dis[x2]-K-dep+1)/2;
		else pth=max(0,dis[x2]+1-K-dep+1)/2;
		maxn=max(maxn,pth);
    }
    if(x1&&dep<pl) _dfs(x1,u,dep+1);
}
void _run(){
    dis[0]=-1;
    ans=max(0,dis[X]+1-K)/2;
    pl=max(0,dis[X]-K)/2;
    tag=(dis[X]-K)%2;
    _dfs(X,0,0);
    ans+=maxn;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("x.in","r",stdin);
    freopen("x.out","w",stdout);
#endif
    _init();
    _pre(X,0);
    _run();
    printf("%d
",ans+1);
    return 0;
}

原文地址:https://www.cnblogs.com/BruceW/p/13149510.html