[NOIp2012]疫情控制 题解
好久没更,强迫自己写一篇。
神 tm 大预言家出的题
注意到如果 \(x\) 小时可以控制住疫情,则 \(\forall x'>x\) 必然也可以控制住疫情,显然答案具有单调性,可以二分答案。
考虑对于当前二分到的答案 \(mid\) 如何 check。根据贪心的策略,我们需要让所有军队的深度尽量小,于是可以考虑每一支军队向上跳的过程,这一步可以通过倍增预处理在 \(\log\) 时间实现。
对于在 \(mid\) 时间内无论如何也跳不到根的子节点的军队,就原地(指最后跳到的地方)驻扎,答案最优。
对于跳至根的子节点后仍有剩余时间的军队 \(s\),分成两种情况:
- 如果剩余时间不能使其在根节点之间跳一个往返,即剩余时间小于 \(2\times dis(s,root)\) 的军队,原地驻扎。
- 反之,将符合此条件的所有军队按照剩余时间排序,并将还未控制住疫情的根的子节点按照距根的距离排序,用双指针将军队与城市一一进行匹配。
考虑这么贪心做的正确性,对于剩余时间不够的军队,如果选择跳过树根去另一个子节点 \(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]疫情控制 题解的更多相关文章
- NOIP2012 疫情控制 题解(LuoguP1084)
NOIP2012 疫情控制 题解(LuoguP1084) 不难发现,如果一个点向上移动一定能控制更多的点,所以可以二分时间,判断是否可行. 但根节点不能不能控制,存在以当前时间可以走到根节点的点,可使 ...
- noip2012疫情控制 题解
题目大意 给出一棵n个节点的树,根是1,要在除根节点以外的点建立检查点,使得从每条根到叶子的路径上都至少存在一个检查点.检查点由军队来建立.初始军队的位置是给定的,移动军队走一条边需要花费这条边的权值 ...
- [NOIP2012]疫情控制 贪心 二分
题面:[NOIP2012]疫情控制 题解: 大体思路很好想,但是有个细节很难想QAQ 首先要求最大时间最小,这种一般都是二分,于是我们二分一个时间,得到一个log. 然后发现一个军队,越往上走肯定可以 ...
- Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增)
Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增) Description H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树, 1 号城市是首都, 也是 ...
- luoguP1084 疫情控制(题解)(搜索+贪心)
luoguP1084 疫情控制 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include& ...
- NOIP2012 D2T3 疫情控制 题解
题面 这道题由于问最大值最小,所以很容易想到二分,但怎么验证并且如何实现是这道题的难点: 首先我们考虑,对于一个军队,尽可能的往根节点走(但一定不到)是最优的: 判断一个军队最远走到哪可以树上倍增来实 ...
- noip2012 疫情控制
[问题描述] H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境城市(叶子 ...
- NOIP2012疫情控制(二分答案+倍增+贪心)
Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...
- [NOIP2012]疫情控制(二分答案+倍增+贪心)
Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...
随机推荐
- 如何实现一个简易版的 Spring - 如何实现 AOP(终结篇)
前言 在 上篇 实现了 判断一个类的方式是符合配置的 pointcut 表达式.根据一个 Bean 的名称和方法名,获取 Method 对象.实现了 BeforeAdvice.AfterReturni ...
- 这 7 个 Linux 命令,你是怎么来使用的?
使用 Linux 系统的开发者,很多人都有自己喜欢的系统命令,下面这个几个命令令是我平常用的比较多的,分享一下. 我不会教科书般的罗列每个指令的详细用法,只是把日常开发过程中的一些场景下,经常使用的命 ...
- javascript中的设计模式
什么是设计模式 设计模式(Design Pattern)是一套被反复使用.多数人知晓的.经过分类的.代码设计经验的总结. 使用设计模式的目的:为了代码可重用性.让代码更容易被他人理解.保证代码可靠性. ...
- 【NX二次开发】判断面是否相切,相切面。
判断面是否相切,相切面. 用到的函数: UF_MODL_ask_minimum_dist 获取两个对象之间的最短距离,以及点坐标 UF_MODL_ask_face_parm 给定一个参考点,返回曲面上 ...
- jQuery04
jQuery内容总结: 1.既然是一个js库,使用之前就要引入js库,是一个.js文件,版本是1.8.3: 2.写一个jQuery页面加载函数(注意与js页面加载函数的区别): $(function( ...
- 不懂就问」CPU 到底是怎么识别代码的?
近读到这样一篇文章,从底层硬件角度出发剖析了一下CPU对代码的识别和读取,内容之精彩,读完感觉学到的很多东西瞬间联系起来了,分享给猿们. 首先要开始这个话题要先说一下半导体.啥叫半导体? 半导体其实就 ...
- c 语言学习第一天
编辑器:Dev-C++ 变量命名(标识符) 变量名只能是英文字母[A-Z,a-z]和数字[0-9]或者下划线[_]组成. 第一个字母必须是字母或者下划线开头. 变量名区分大小写.例如:Fish≠fis ...
- Vue(4)Vue指令的学习1
前言 Vue官网一共有提供了14个指令,分别如下 v-text v-html v-show v-if ☆☆☆ v-else ☆☆☆ v-else-if ☆☆☆ v-for ☆☆☆ v-on ☆☆☆ v ...
- rust漫游 - 写时拷贝 Cow<'_, B>
rust漫游 - 写时拷贝 Cow<'_, B> Cow 是一个写时复制功能的智能指针,在数据需要修改或者所有权发生变化时使用,多用于读多写少的场景. pub enum Cow<'a ...
- 13、windows下卸载oracle
13.1.停用oracle服务: 进入计算机管理,在服务中,找到oracle开头的所有服务,右击选择停止: 13.2.删除oracle: 在开始菜单中,找到oracle->Universal I ...