好久没更,强迫自己写一篇。

神 tm 大预言家出的题


注意到如果 \(x\) 小时可以控制住疫情,则 \(\forall x'>x\) 必然也可以控制住疫情,显然答案具有单调性,可以二分答案。

考虑对于当前二分到的答案 \(mid\) 如何 check。根据贪心的策略,我们需要让所有军队的深度尽量小,于是可以考虑每一支军队向上跳的过程,这一步可以通过倍增预处理在 \(\log\) 时间实现。

对于在 \(mid\) 时间内无论如何也跳不到根的子节点的军队,就原地(指最后跳到的地方)驻扎,答案最优。

对于跳至根的子节点后仍有剩余时间的军队 \(s\),分成两种情况:

  1. 如果剩余时间不能使其在根节点之间跳一个往返,即剩余时间小于 \(2\times dis(s,root)\) 的军队,原地驻扎。
  2. 反之,将符合此条件的所有军队按照剩余时间排序,并将还未控制住疫情的根的子节点按照距根的距离排序,用双指针将军队与城市一一进行匹配。

考虑这么贪心做的正确性,对于剩余时间不够的军队,如果选择跳过树根去另一个子节点 \(s'\) 驻扎,则必然 \(dis(root,s)>dis(root,s')\),这么做可能会导致需要一个剩余时间足够的军队 \(s''\) 从其本来位置跨过根跳至 \(s\),花费时间 \(dis(root,s'')+dis(root,s)\),而这样做花的时间显然比 \(s\) 原地驻扎,\(s''\) 跨过根跳至 \(s'\) 的情况长,于是贪心策略正确。

当然,也有可能存在一个军队剩余时间不够,但是其子树已被另一个军队控制的情况,那么这个军队就应算入到上文的第二种情况中。

具体实现较为麻烦,可以根据个人编码习惯写,这里不想再赘述。

#include <bits/stdc++.h>
#define mem(a) memset(a,0,sizeof(a))
#define st first
#define nd second
using namespace std;
typedef long long ll; const int N=5e4+5;
struct Edge{int to,nxt,w;}e[N<<1];
int head[N],n,m,cnt,fa[N][18],sod[N],dep[N];
ll dis[N],ans,mch[N],mch2[N];
bool fl,stay[N],mst[N];
pair<ll,int> h[N]; inline void add(int u,int v,int w) {e[++cnt]=(Edge){v,head[u],w};head[u]=cnt;} void dfs0(int u,int fat)
{
fa[u][0]=fat; dep[u]=dep[fat]+1;
for(int i=1;(1<<i)<=dep[u];++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=head[u],v;i;i=e[i].nxt)
{
v=e[i].to; if(v==fat) continue;
dis[v]=dis[u]+e[i].w;
dfs0(v,u);
}
} bool dfs(int u)
{
if(stay[u]) return 1;
bool hasSon=0;
for(int i=head[u],v;i;i=e[i].nxt)
{
v=e[i].to; if(v==fa[u][0]) continue;
hasSon=1;
if(!dfs(v)) return 0;
}
return hasSon;
} bool check(ll lim)
{
mem(stay); mem(mch);
mem(mch2); mem(h); mem(mst);
int toth=0,tota=0,totb=0;
for(int i=1;i<=m;++i)
{
ll sum=0; int x=sod[i];
for(int j=log2(dep[x]);~j;--j)
if(fa[x][j]>1&&sum+dis[x]-dis[fa[x][j]]<=lim)
sum+=dis[x]-dis[fa[x][j]],x=fa[x][j];
if(fa[x][0]==1&&sum+dis[x]<=lim) h[++toth]={lim-sum-dis[x],x};
else stay[x]=1;
}
for(int i=head[1];i;i=e[i].nxt)
if(!dfs(e[i].to)) mst[e[i].to]=1;
for(int i=1;i<=toth;++i)
if(mst[h[i].nd]&&h[i].st<dis[h[i].nd]) mst[h[i].nd]=0;
else mch[++tota]=h[i].st;
for(int i=head[1];i;i=e[i].nxt)
if(mst[e[i].to]) mch2[++totb]=dis[e[i].to];
if(tota<totb) return 0;
sort(mch+1,mch+tota+1);
sort(mch2+1,mch2+totb+1);
int i=1,j=1;
while(i<=tota&&j<=totb)
if(mch[i]>=mch2[j]) ++i,++j;
else ++i;
return j>totb;
} int main()
{
scanf("%d",&n); ll sum=0;
for(int i=1,a,b,c;i<n;++i)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); add(b,a,c);
sum+=c;
}
dfs0(1,0);
scanf("%d",&m);
for(int i=1;i<=m;++i) scanf("%d",&sod[i]);
ll l=0,r=sum;
while(l<=r)
{
ll mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1,fl=1;
else l=mid+1;
}
printf("%lld",fl?ans:-1);
return 0;
}

[NOIp2012]疫情控制 题解的更多相关文章

  1. NOIP2012 疫情控制 题解(LuoguP1084)

    NOIP2012 疫情控制 题解(LuoguP1084) 不难发现,如果一个点向上移动一定能控制更多的点,所以可以二分时间,判断是否可行. 但根节点不能不能控制,存在以当前时间可以走到根节点的点,可使 ...

  2. noip2012疫情控制 题解

    题目大意 给出一棵n个节点的树,根是1,要在除根节点以外的点建立检查点,使得从每条根到叶子的路径上都至少存在一个检查点.检查点由军队来建立.初始军队的位置是给定的,移动军队走一条边需要花费这条边的权值 ...

  3. [NOIP2012]疫情控制 贪心 二分

    题面:[NOIP2012]疫情控制 题解: 大体思路很好想,但是有个细节很难想QAQ 首先要求最大时间最小,这种一般都是二分,于是我们二分一个时间,得到一个log. 然后发现一个军队,越往上走肯定可以 ...

  4. Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增)

    Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增) Description H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树, 1 号城市是首都, 也是 ...

  5. luoguP1084 疫情控制(题解)(搜索+贪心)

    luoguP1084 疫情控制 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include& ...

  6. NOIP2012 D2T3 疫情控制 题解

    题面 这道题由于问最大值最小,所以很容易想到二分,但怎么验证并且如何实现是这道题的难点: 首先我们考虑,对于一个军队,尽可能的往根节点走(但一定不到)是最优的: 判断一个军队最远走到哪可以树上倍增来实 ...

  7. noip2012 疫情控制

    [问题描述] H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境城市(叶子 ...

  8. NOIP2012疫情控制(二分答案+倍增+贪心)

    Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...

  9. [NOIP2012]疫情控制(二分答案+倍增+贪心)

    Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...

随机推荐

  1. 【Java实现】剑指offer53.1——在排序数组中查找数字(LeetCode34:在排序数组中查找元素的起始位置)

    序数组中查找元素的起始位置):思路分享 <剑指offer>题目和LeetCode主站本质是一样的,想要找到target数目,也需要找到左右边界 题目解析: 在一个排序数组中,找到targe ...

  2. Class.forName()、Class.forName().newInstance() 、New 三者区别!

    终于明白为什么加载数据库驱动只用Class.forName()了!!困扰了我2个小时!!希望我写的这个东西对各位有所帮助.      在Java开发特别是数据库开发中,经常会用到Class.forNa ...

  3. Java线程池底层源码分享和相关面试题(持续更新)

    线程池各个参数讲解 public ThreadPoolExecutor(int corePoolSize, //线程池核心工作线程数量,比如newFixedThreadPool中可以自定义的线程数量就 ...

  4. 08:jQuery(01)

    今日内容概要 jQuery(封装了js的前端框架(模块)) 很容易与DOM操作混淆 jQuery """ jQuery内部封装了原生的js代码(还额外添加了很多的功能) ...

  5. SpringBoot 自动配置原理,翻源码看一下

    ​ 初始化一个Springboot项目,在主启动类会有这么一个注解:@SpringBootApplication,自动装配的秘密全在主启动类这个注解里面了 点进去一层会发现有三个子注解组成,分别是 @ ...

  6. MySQL的可重复读级别能解决幻读问题吗?

    之前在深入了解数据库理论的时候,了解到事务的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了. 但是对于幻读, ...

  7. 使用Go module和GoLand初始化一个Go项目

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 Golang是一门优秀的语言,特别是在并发编程上,得益于它的协程和channel等,非常方便易用.它通过go m ...

  8. Feign Client 原理和使用

    Feign Client 原理和使用 一块石头 ​ 公众号:好奇心森林 ​关注他 创作声明:内容包含虚构创作 6 人赞同了该文章 最近一个新项目在做后端HTTP库技术选型的时候对比了Spring We ...

  9. 进程与线程 .Net Core系列-多线程

    进程与线程 进程: 狭义定义:进程是正在运行的程序的实例 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动.它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分 ...

  10. (Vue中)cehart在同一个dom上画图图切换时饼图有折线图的坐标系

    网上都是别人转载的,下面是转载的代码,在Vue中根本不适用 var echartrunningstate = null; if (echartrunningstate && echar ...