P1045 战争游戏 官方题解
Gioush OJ · P1045 战争游戏
战争游戏
【题意简述】
给出一棵以 为根的带权树以及若干名特工的初始位置。特工可以同时移动,但不能潜伏在根结点。要求每条根到叶子的路径上最终至少有一名特工,求最短时间;无解时输出 Pity!。
【Hint】
【提示】
二分时间。无法到达根的特工尽量向上移动并覆盖原子树;能够到达根的特工先处理必须留在原分支的情况,再与未覆盖根分支按剩余时间贪心匹配。
【数据点 1∼101\sim 101∼10】
二分答案后,可以枚举每名特工的潜伏位置;叶子数较少时,也可以把一个结点能够覆盖的叶子集合压成二进制状态,进行集合 DP。这些做法说明固定时间后的判定只包含两部分:特工能否到达某个位置,以及全部叶子路径是否被覆盖。
【数据点 11∼1911\sim 1911∼19】
当根只有一个儿子时,不存在根分支之间的调度。每名特工尽量向上移动,再递归判断子树是否被覆盖即可。
当树为菊花图时,能够到达根的特工只需记录剩余时间。若某名特工回到根后无法再次进入自己的原分支,而这个分支尚未被覆盖,就应先把同分支中剩余时间最小的特工留在原分支。剩余特工与剩余需求分别排序后贪心匹配。
【正解】
答案具有单调性,因此二分时间 。倍增预处理每个结点的祖先以及向上移动的距离。
对于每名特工,先在不越过根的条件下尽量向上跳。若它无法到达根,就标记它能够到达的最高结点。它无法进入其他根分支,停在这里能够覆盖尽量大的原子树,所以一定不劣。
定义 表示 的子树内全部叶子路径是否已经被局部特工截断,则
若 被标记,所有经过 的路径均已截断;否则必须让每个儿子子树分别被截断。因此这个递推同时具有充分性与必要性。对根的每个儿子分别检查,记录尚未覆盖的分支。
若一名特工能够到达根,记其原根分支为 ,到达根后的剩余时间为
它能够进入根儿子 ,当且仅当 。
不能立即把所有特工投入统一匹配。若分支 尚未覆盖,并且来自 的最小剩余时间小于 ,就让这名特工留在原分支。若一个可行方案没有这样做,则分支 必须由另一名能力更强的特工负责;交换两名特工后,较弱特工留在原分支,较强特工接替原任务,方案仍然可行。
完成预留后,把剩余时间与未覆盖分支的根边权分别从小到大排序。若当前特工不能满足最小需求,它也不能满足后续需求,可以跳过;否则让它负责当前最小需求。交换论证说明这一贪心是充要的。
令 为全部边权之和。若时间 仍不可行,则问题无解;否则二分得到最小可行时间。
【复杂度分析】
一次判定包含倍增移动、树上覆盖与排序,时间复杂度为 。总时间复杂度为 ,空间复杂度为 。
【参考代码】
/*Author:EhundateghDate:2026/7/30Name:wargame.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 50010using namespace std;int T,n,m,Head[MAXN],Total=0,Fa[MAXN][20],Army[MAXN];int RootVal[MAXN],MinID[MAXN],NeedVal[MAXN],cn=0,cf=0,cr=0;long long Value[MAXN][20],SumEdge=0,RestList[MAXN];bool Mark[MAXN],Need[MAXN],Used[MAXN];struct edge{ int St,Ed,Next; int Value;}Edge[MAXN<<1];struct army{ long long Rest; int Root;}Free[MAXN];void Edge_Add(int St,int Ed,int Value){ Edge[++Total]={St,Ed,Head[St],Value}; Head[St]=Total;}void DFS(int Now,int From){ for(int i=Head[Now];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(To==From) continue; Fa[To][0]=Now; Value[To][0]=Edge[i].Value; if(Now==1) RootVal[To]=Edge[i].Value; DFS(To,Now); }}bool Cover(int Now,int From){ if(Mark[Now]) return true; bool HasSon=false; for(int i=Head[Now];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(To==From) continue; HasSon=true; if(!Cover(To,Now)) return false; } return HasSon;}bool cmp(army a,army b){return a.Rest<b.Rest;}bool Judge(long long Limit){ memset(Mark,0,sizeof(bool)*(n+2)); memset(Need,0,sizeof(bool)*(n+2)); memset(Used,0,sizeof(bool)*(m+2)); memset(MinID,0,sizeof(int)*(n+2)); cf=cn=cr=0; for(int i=1;i<=m;i++){ int Now=Army[i]; long long UsedValue=0; for(int j=17;j>=0;j--){ if(Fa[Now][j]>1&&UsedValue+Value[Now][j]<=Limit){ UsedValue+=Value[Now][j]; Now=Fa[Now][j]; } } if(Fa[Now][0]==1&&UsedValue+Value[Now][0]<=Limit){ Free[++cf]={Limit-UsedValue-Value[Now][0],Now}; } else Mark[Now]=true; } for(int i=Head[1];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(!Cover(To,1)) Need[To]=true; } sort(Free+1,Free+cf+1,cmp); for(int i=1;i<=cf;i++){ int Root=Free[i].Root; if(!MinID[Root]) MinID[Root]=i; } for(int i=Head[1];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(!Need[To]) continue; int Pos=MinID[To]; if(Pos&&Free[Pos].Rest<RootVal[To]){ Used[Pos]=true; Need[To]=false; } } for(int i=Head[1];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(Need[To]) NeedVal[++cn]=RootVal[To]; } for(int i=1;i<=cf;i++){ if(!Used[i]) RestList[++cr]=Free[i].Rest; } sort(NeedVal+1,NeedVal+cn+1); sort(RestList+1,RestList+cr+1); int Pos=1; for(int i=1;i<=cr&&Pos<=cn;i++){ if(RestList[i]>=NeedVal[Pos]) Pos++; } return Pos>cn;}void Solve(){ scanf("%d%d",&n,&m); memset(Head,0,sizeof(int)*(n+2)); memset(Fa,0,sizeof(Fa[0])*(n+2)); memset(Value,0,sizeof(Value[0])*(n+2)); memset(RootVal,0,sizeof(int)*(n+2)); Total=0;SumEdge=0; int In1,In2,In3; for(int i=1;i<n;i++){ scanf("%d%d%d",&In1,&In2,&In3); Edge_Add(In1,In2,In3); Edge_Add(In2,In1,In3); SumEdge+=In3; } for(int i=1;i<=m;i++) scanf("%d",&Army[i]); DFS(1,0); for(int j=1;j<=17;j++){ for(int i=1;i<=n;i++){ Fa[i][j]=Fa[Fa[i][j-1]][j-1]; Value[i][j]=Value[i][j-1]+Value[Fa[i][j-1]][j-1]; } } if(!Judge(SumEdge)){puts("Pity!");return;} long long Left=0,Right=SumEdge; while(Left<Right){ long long Mid=(Left+Right)>>1; if(Judge(Mid)) Right=Mid; else Left=Mid+1; } printf("%lld\n",Left);}int main(){ int c; scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}