P1045 · OFFICIAL SOLUTION

P1045 战争游戏 官方题解

Gioush OJ · P1045 战争游戏

战争游戏

【题意简述】

给出一棵以 11 为根的带权树以及若干名特工的初始位置。特工可以同时移动,但不能潜伏在根结点。要求每条根到叶子的路径上最终至少有一名特工,求最短时间;无解时输出 Pity!

【Hint】

【提示】

二分时间。无法到达根的特工尽量向上移动并覆盖原子树;能够到达根的特工先处理必须留在原分支的情况,再与未覆盖根分支按剩余时间贪心匹配。

【数据点 1∼101\sim 101∼10】

二分答案后,可以枚举每名特工的潜伏位置;叶子数较少时,也可以把一个结点能够覆盖的叶子集合压成二进制状态,进行集合 DP。这些做法说明固定时间后的判定只包含两部分:特工能否到达某个位置,以及全部叶子路径是否被覆盖。

【数据点 11∼1911\sim 1911∼19】

当根只有一个儿子时,不存在根分支之间的调度。每名特工尽量向上移动,再递归判断子树是否被覆盖即可。

当树为菊花图时,能够到达根的特工只需记录剩余时间。若某名特工回到根后无法再次进入自己的原分支,而这个分支尚未被覆盖,就应先把同分支中剩余时间最小的特工留在原分支。剩余特工与剩余需求分别排序后贪心匹配。

【正解】

答案具有单调性,因此二分时间 Limit\operatorname{Limit}。倍增预处理每个结点的祖先以及向上移动的距离。

对于每名特工,先在不越过根的条件下尽量向上跳。若它无法到达根,就标记它能够到达的最高结点。它无法进入其他根分支,停在这里能够覆盖尽量大的原子树,所以一定不劣。

定义 Cover(u)\operatorname{Cover}(u) 表示 uu 的子树内全部叶子路径是否已经被局部特工截断,则

Cover(u)=Mark(u)(u 不是叶子vson(u)Cover(v)).\operatorname{Cover}(u)=\operatorname{Mark}(u)\lor \left(u\text{ 不是叶子}\land \bigwedge_{v\in\operatorname{son}(u)}\operatorname{Cover}(v)\right).

uu 被标记,所有经过 uu 的路径均已截断;否则必须让每个儿子子树分别被截断。因此这个递推同时具有充分性与必要性。对根的每个儿子分别检查,记录尚未覆盖的分支。

若一名特工能够到达根,记其原根分支为 rr,到达根后的剩余时间为

Rest=Limitdist(Army,1).\operatorname{Rest}=\operatorname{Limit}-\operatorname{dist}(\operatorname{Army},1).

它能够进入根儿子 vv,当且仅当 Restw(1,v)\operatorname{Rest}\geq w(1,v)

不能立即把所有特工投入统一匹配。若分支 rr 尚未覆盖,并且来自 rr 的最小剩余时间小于 w(1,r)w(1,r),就让这名特工留在原分支。若一个可行方案没有这样做,则分支 rr 必须由另一名能力更强的特工负责;交换两名特工后,较弱特工留在原分支,较强特工接替原任务,方案仍然可行。

完成预留后,把剩余时间与未覆盖分支的根边权分别从小到大排序。若当前特工不能满足最小需求,它也不能满足后续需求,可以跳过;否则让它负责当前最小需求。交换论证说明这一贪心是充要的。

VV 为全部边权之和。若时间 VV 仍不可行,则问题无解;否则二分得到最小可行时间。

【复杂度分析】

一次判定包含倍增移动、树上覆盖与排序,时间复杂度为 O((n+m)logn)\mathcal{O}((n+m)\log n)。总时间复杂度为 O((n+m)lognlogV)\mathcal{O}((n+m)\log n\log V),空间复杂度为 O(nlogn)\mathcal{O}(n\log n)

【参考代码】

/*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;}