P1084 疫情控制
这是一道 $NOIP$ 难度的题
首先贪心的想法很显然,每个军队都尽量往根跳,因为越往上控制的越多
但是怎么给每个军队分配终点不太好搞,那就二分一个答案
此时每个军队如果没法跳到根,那就直接停下就好了,现在考虑那些能跳到根节点的军队
此时可能根节点剩下一些儿子没有封锁,那么只要考虑给这些军队安排封锁儿子即可
因为到不同儿子需要的时间不同,各个军队的剩余时间也不同,那么先排序然后贪心地让需要时间长的和剩下时间多的一个个匹配即可
但是要注意一点细节,如果一个军队能到达终点,但是剩下的时间不够返回原本所在的儿子子树的根节点
那么可能有一种比较优的情况是这个军队根本不用到根,直接在儿子停下,但是我们却让它走到根,之后就没法回来了
问题不大,可以发现我们直接把这个军队 $q$ 安排给儿子(如果有需要)一定比走到根更优
因为如果它走到根而让其他子树来的军队 $p$ 到自己子树的根(而 $q$ 不行),说明 $p$ 剩下的时间一定比 $q$ 多
那么还不如让 $q$ 直接停下,就可以腾出 $p$ 可以有更大的用处
如果某个儿子子树多个军队都是有去无回的情况,那么把最小的停下就好
总结一下,二分答案,然后贪心跳,再贪心匹配,至于判断一个儿子是否需要军队可以一遍简单的类似树形 $dp$ 的过程处理
贪心跳的时候可以倍增,那么复杂度 $n \log^2 n$($n,m$ 同阶)
事实上可以不用跳,只要做一遍类似树形 $dp$ 的过程,对于某个初始在所有军队上面的节点(如果在下面显然一定封锁了)
如果到初始军队的最近距离小于等于二分的答案或者儿子全封锁那么这个节点也封锁了
那么就可以知道哪些儿子需要别的子树的军队
并且初始预先把各个儿子子树内的军队按到根的距离排序,之后只要归并一下就好了
然后就可以做到 $n \log n$
代码极丑,不建议参考
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> #include<set> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=3e5+7; const ll INF=1e18; int n,m,pos[N]; int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt; inline void add(int a,int b,int c) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; val[cntt]=c; } int f[N][21],dep[N]; ll dis[N]; void pre_dfs(int x,int fa) { f[x][0]=fa; dep[x]=dep[fa]+1; for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1]; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa) continue; dis[v]=dis[x]+val[i]; pre_dfs(v,x); } } inline int jump(int x,ll t)//x出发跳t时间最多能跳到哪 { for(int i=20;i>=0;i--) if(f[x][i]&&dis[x]-dis[f[x][i]]<=t) t-=dis[x]-dis[f[x][i]],x=f[x][i]; return x; } bool h[N],son[N];//h判断是否被封锁,son判断是否为根节点的儿子 vector <int> Army[N];//根节点每个儿子子树的军队 multiset <ll> S[N];//根节点每个儿子子树内军队的多余时间 int tot,id[N];//第i个儿子的编号 inline int jump_son(int x)//跳到最近的根节点儿子 { if(son[x]) return x; for(int i=20;i>=0;i--) if(f[x][i]>1&&!son[f[x][i]]) x=f[x][i]; return f[x][0]; } void dfs(int x,int fa)//处理h { if(h[x]) return; bool flag=1,son=0; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa) continue; dfs(v,x); son=1; flag&=h[v]; } if(son) h[x]=flag; } bool check(ll mid) { memset(h,0,sizeof(h)); for(int i=1;i<=tot;i++) { S[id[i]].clear(); for(int x: Army[id[i]]) { int v=jump(x,mid); if(v!=1) h[v]=1;//跳不到根就直接停下 else S[id[i]].insert(mid-dis[x]); //这里插入到set可以预先按dis排序然后直接插入到vector,复杂度降至O(m) } } dfs(1,0); for(int i=1;i<=tot;i++) { if(h[id[i]]||(!S[id[i]].size())) continue; if(*S[id[i]].begin()>dis[id[i]]) continue; S[id[i]].erase(S[id[i]].begin()); h[id[i]]=1; //如果有去无回就让最少剩余时间的军队停在儿子 } vector <ll> tmp,fre; for(int i=1;i<=tot;i++) { for(ll x: S[id[i]]) fre.push_back(x); if(!h[id[i]]) tmp.push_back(dis[id[i]]); } sort(tmp.begin(),tmp.end()); sort(fre.begin(),fre.end()); //这里排序的复杂度可以归并降到O(m) int len=tmp.size(),r=fre.size()-1; for(int i=len-1;i>=0;i--)//贪心匹配 { if((r<0)||fre[r]<tmp[i]) return 0; r--; } return 1; } int main() { n=read(); int a,b,c; ll L=0,R=0,ans=-1; for(int i=1;i<n;i++) { a=read(),b=read(),c=read(); add(a,b,c),add(b,a,c); R+=c; } pre_dfs(1,0); for(int i=fir[1];i;i=from[i]) son[to[i]]=1,id[++tot]=to[i]; m=read(); for(int i=1;i<=m;i++) { pos[i]=read(); int x=jump_son(pos[i]); Army[x].push_back(pos[i]); } while(L<=R) { ll mid=L+R>>1; if(check(mid)) ans=mid,R=mid-1; else L=mid+1; } printf("%lld\n",ans); return 0; }