Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增)
Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增)
Description
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树, 1 号城市是首都, 也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境 城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境 城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是, 首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在 一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等 于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
Input
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行3个整数,u、v、w,每两个整数之间用一个空格隔开,表示从 城市 u 到城市v有一条长为w的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示军队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎 的城市的编号。
Output
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
Sample Input
4
1 2 1
1 3 2
3 4 3
2
2 2
Sample Output
3
Http
Luogu:https://www.luogu.org/problem/show?pid=1084
Source
二分,贪心,倍增
题目大意
给出一棵树,树上有若干个点有军队驻扎。现在求最小的军队行动时间使得军队移动使得从根1到任意一个儿子节点的路径上至少有一个军队把守
解决思路
首先考虑不可能的情况。我们定义根节点1的直属儿子为第一层,如果这个数据无解,当且仅当数据中的军队数小于第一层的点数,因为这样无论如何无法控制所有节点。而若时间无限的话,可以将所有军队调动到第一层,这样保证可以控制所有节点。
时间无限?这也就意味着一定存在一个时间点,使得在当前时间点及以后能够满足军队控制所有点,而再往前一点点就不行了。我们发现时间满足单调性
既然满足单调性,我们就二分时间,判断这个时间点是否可以让军队控制所有节点。
这个题最难的地方就是如何判断了(即check)
首先比较好想的是,在我们当前二分的时间\(mid\)下,所有的军队都是尽量向上跳,越上越好。因为是满足树的关系的,所以深度越浅,能控制的点就不会差。需要注意的是,这里需要提前处理好倍增数组,利用倍增加速跳的过程
接下来我们要把军队分为两部分,一部分是不管怎么跳都跳不到根节点1的,这些点就让它留在它能到的最浅的地方;另一部分是可以调到根节点1的,这也就意味着这些点可以通过1后到达第一层的其他点,控制1的其他子树,我们先把这些军队记录,同时存下这些军队剩余的时间和他的来源(指从它的出发点向上到达的第一层的点)。
然后我们首先来处理第一部分的点。对于一个点,如果它的儿子都已经被军队控制,那么它也相当于被军队控制,比如说这个例子:

我们用橙色的点代表已经有军队控制的点,蓝色代表未控制。那么此时,两个蓝色点其实相对的也是被控制了的。这个过程我们可以用一个dfs来完成。
然后就是处理能到达1的军队。首先贪心的想一想,如果我们要让一个军队经过1到达另外一棵子树,我们只让它到第一层就可以了。所以,我们将所有军队按照剩余时间排序,将所有当前还未控制的第一层的点按照距离1的距离排序。贪心地让剩余时间最少的军队匹配能匹配的最大的。这里需要注意的是,如果我们枚举到一个军队时,发现它的来源还没有被控制,那么让它去控制它的来源。因为这意味着这个军队的来源在排序拍在后面,有可能无法匹配,而我们让这个军队撤回去一定会更优。
另外需要注意的是,这道题有一个很容易犯的错误,就是在标记出能到达1的军队后,先检查这些军队的来源有没有控制,如果没有就让剩余时间最小的来控制,这个贪心的是错误的,具体请看下面这个例子:

黄色的是边权,红色绿色和紫色的分别代表三只军队和剩余的时间。所有的点现在都没有控制。如果我们按照上面的贪心算法,先让军队去匹配它的来源,那么就是这样:

我们发现最右边的点没有被控制,那么是否意味着这是不可行的呢?
不,我们有这种走法:

这样走,三个点都可以控制,所以,那种贪心方法是不正确的。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define mem(Arr,x) memset(Arr,x,sizeof(Arr))
const int maxN=100001;
const int maxTwo=20;
const ll inf=1e15;
class Edge//边,前向星
{
public:
ll u,v,w;
};
class sort_data//用来排序的,这是在check中记录能到达根节点的点
{
public:
ll pos,resttime;//来源(第一层的点),剩余时间
};
bool operator < (sort_data A,sort_data B)//因为要排序,所以重载小于运算符
{
return A.resttime<B.resttime;
}
class Destination//记录还没有控制的第一层的点,同时记录它与1的距离
{
public:
ll pos,tim;//点编号,与1的距离
};
bool operator < (Destination A,Destination B)//重载小于运算
{
return A.tim<B.tim;
}
ll n,m;
int cnt;
ll Ans;//记录答案
int Head[maxN];//图
Edge E[maxN];//图
int Next[maxN];//图
ll Armypos[maxN];//记录每一个军队的初始所在点
ll Skip[25][maxN];//倍增数组,Skip[i][j]代表j号点向上跳2^j个点所到的位置
ll Skip_path[25][maxN];//倍增数组,Skip_path[i][j]代表对应的Skip[i][j]跳的距离
ll Getpos[maxN];//在check中记录每个军队向上跳最浅能跳到的点
bool is_cover[maxN];//在check中记录某个点是否已经被控制
ll read();
void Add_Edge(int u,int v,int w);//添加边
void Skip_dfs(int u,int father);//初始化倍增的信息
bool check(int mid);//二分检查
void check_dfs(int u,int father);//dfs检查,即这个用来处理把那些儿子都已经被控制的点也置为已控制
int main()
{
n=read();
cnt=0;
mem(Head,-1);
for (int i=1;i<n;i++)//读入树边
{
int u=read(),v=read(),w=read();
Add_Edge(u,v,w);
}
//开始构造倍增数组
mem(Skip,-1);
mem(Skip_path,0);
Skip_dfs(1,1);
for (int i=1;i<=maxTwo;i++)
for (int j=1;j<=n;j++)
if (Skip[i-1][j]!=-1)
{
Skip[i][j]=Skip[i-1][Skip[i-1][j]];
Skip_path[i][j]=Skip_path[i-1][j]+Skip_path[i-1][Skip[i-1][j]];
}
//构造完毕
m=read();//开始读入军队
for (int i=1;i<=m;i++)
Armypos[i]=read();
int l=0,r=300000;//二分
Ans=inf;
do
{
int mid=(l+r)/2;
if (check(mid))
{
Ans=min(Ans,(ll)(mid));
r=mid-1;
}
else
l=mid+1;
}
while (l<=r);
cout<<Ans<<endl;
return 0;
}
ll read()//快速读入
{
ll x=0;
char ch=getchar();
while ((ch>'9')||(ch<'0'))
ch=getchar();
while ((ch>='0')&&(ch<='9'))
{
x=x*10+ch-48;
ch=getchar();
}
return x;
}
void Add_Edge(int u,int v,int w)
{
cnt++;
Next[cnt]=Head[u];
Head[u]=cnt;
E[cnt].u=u;
E[cnt].v=v;
E[cnt].w=w;
cnt++;
Next[cnt]=Head[v];
Head[v]=cnt;
E[cnt].u=v;
E[cnt].v=u;
E[cnt].w=w;
return;
}
void Skip_dfs(int u,int father)//构造倍增初始数据,即Skip[0][i]和Skip_path[0][i]
{
for (int i=Head[u];i!=-1;i=Next[i])
{
int v=E[i].v;
if (v==father)
continue;
if (Skip[0][v]==-1)
{
Skip[0][v]=u;
Skip_path[0][v]=E[i].w;
Skip_dfs(v,u);
}
}
return;
}
bool check(int mid)//二分检查
{
mem(is_cover,0);
vector<sort_data> V;//记录能到达1的军队
V.clear();
for (int i=1;i<=m;i++)//让每一个军队都尽量向上跳
{
Getpos[i]=Armypos[i];//初始位置就是军队所在的位置
ll timecnt=0;//记录当前已花的时间
for (int j=maxTwo;j>=0;j--)
if ((Skip[j][Getpos[i]]>1)&&(timecnt+Skip_path[j][Getpos[i]]<=mid))//注意这里>1,
{
timecnt=timecnt+Skip_path[j][Getpos[i]];
Getpos[i]=Skip[j][Getpos[i]];
}
if ((Skip[0][Getpos[i]]==1)&&(Skip_path[0][Getpos[i]]+timecnt<mid))//当还能向上跳一次并且跳到1时,记录
{
V.push_back((sort_data){Getpos[i],mid-timecnt-Skip_path[0][Getpos[i]]});
}
else//否则则停在这里,直接控制这课子树
is_cover[Getpos[i]]=1;
}
check_dfs(1,1);//dfs检查控制
sort(V.begin(),V.end());//对剩余军队按剩余时间升序排序
vector<Destination> D;//记录还没有被控制的第一层节点
D.clear();
for (int i=Head[1];i!=-1;i=Next[i])
{
int v=E[i].v;
if (is_cover[v]==0)
D.push_back((Destination){v,E[i].w});
}
sort(D.begin(),D.end());//排序第一层点
if (D.size()>V.size())//当剩余军队数小于剩余第一层点数时,不管怎么呢调派军队都无法满足,返回不可行
return 0;
int j=0;//记录当前匹配到第几个点
if (D.size()==0)//若所有第一层点都已匹配,返回可行
return 1;
for (int i=0;i<V.size();i++)//i从小到大枚举每一个军队
{
if (is_cover[V[i].pos]==0)//若当前军队的来源还未控制,则让这支军队直接控制其来源,这样更划算
{
is_cover[V[i].pos]=1;
continue;
}
while ((is_cover[D[j].pos]==1)&&(j<D.size()))//因为有上面这种操作,所以先要让j跳到第一个还未匹配的第一层节点
j++;
if (j==D.size())//当j到末尾时,返回可行
return 1;
if (V[i].resttime>=D[j].tim)//判断当前军队是否可以去控制这个点,如果可以,则去控制
{
is_cover[D[j].pos]=1;
j++;
}
if (j==D.size())
return 1;
}
while ((is_cover[D[j].pos]==1)&&(j<D.size()))//最后再让j向后跳一次
j++;
if (j==D.size())//当到末尾时,返回可行
return 1;
return 0;//否则返回不可行
}
void check_dfs(int u,int father)
{
if (is_cover[u]==1)//已经被控制,直接返回
return;
bool is_all=1;//记录是不是所有的儿子都已经控制
bool has_son=0;//记录是否有儿子
for (int i=Head[u];i!=-1;i=Next[i])
{
int v=E[i].v;
if (v==father)
continue;
has_son=1;
check_dfs(v,u);
if (is_cover[v]==0)
is_all=0;
}
if (has_son==0)//当没有儿子时,肯定没有被控制
is_all=0;
if (is_all==1)//若所有儿子都被控制,则当前也标记为被控制
is_cover[u]=1;
return;
}
Luogu 1084 NOIP2012 疫情控制 (二分,贪心,倍增)的更多相关文章
- luogu1084 [NOIp2012]疫情控制 (二分答案+倍增+dfs序)
先二分出一个时间,把每个军队倍增往上跳到不能再跳 然后如果它能到1号点,就记下来它跳到1号点后剩余的时间:如果不能,就让它就地扎根,记一记它覆盖了哪些叶节点(我在这里用了dfs序+差分,其实直接dfs ...
- LUOGU P1084 疫情控制(二分+贪心+树上倍增)
传送门 解题思路 比较神的一道题.首先发现是最小值问题,并且具有单调性,所以要考虑二分答案.其次有一个性质是军队越靠上越优,所以我们要将所有的军队尽量向上提,这一过程我们用倍增实现.发现这时有两种军队 ...
- NOIP2012疫情控制(二分答案+树上贪心)
H 国有n个城市,这 n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示 ...
- 洛谷P1084 疫情控制(贪心+倍增)
这个题以前写过一遍,现在再来写,感觉以前感觉特别不好写的细节现在好些多了,还是有进步吧. 这个题的核心思想就是贪心+二分.因为要求最小时间,直接来求问题将会变得十分麻烦,但是如果转换为二分答案来判断可 ...
- Luogu P1084 [NOIP2012]疫情控制
题目 首先我们二分一下答案. 然后我们用倍增让军队往上跳,最多先跳到根的子节点. 如果当前军队可以到达根节点,那么记录一下它的编号和它到达根节点后还可以走的时间. 并且我们记录根节点的叶子节点上到根节 ...
- [NOIP2012]疫情控制 贪心 二分
题面:[NOIP2012]疫情控制 题解: 大体思路很好想,但是有个细节很难想QAQ 首先要求最大时间最小,这种一般都是二分,于是我们二分一个时间,得到一个log. 然后发现一个军队,越往上走肯定可以 ...
- NOIP2012 疫情控制 题解(LuoguP1084)
NOIP2012 疫情控制 题解(LuoguP1084) 不难发现,如果一个点向上移动一定能控制更多的点,所以可以二分时间,判断是否可行. 但根节点不能不能控制,存在以当前时间可以走到根节点的点,可使 ...
- NOIP2012疫情控制(二分答案+倍增+贪心)
Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...
- [NOIP2012]疫情控制(二分答案+倍增+贪心)
Description H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点. H国的首都爆发了一种危害性极高的传染病.当局为了控制疫情,不让疫情扩散到边境 ...
随机推荐
- JQuery_自带的动画效果
1.方法: show:显示选中元素. hide:隐藏选中元素. toggle:显示或隐藏选中元素. fadeIn:将选中元素的不透明度逐步提升到100%. fadeOut:将选中元素的不透明度逐步降为 ...
- Docker容器学习梳理 - SSH方式登陆容器
前面几篇已经介绍了Docker基础环境的部署,下面介绍下通过ssh方式登陆Docker容器的操作记录(其实不太建议直接用ssh去连接上容器的想法,虽然可以,但是有很多弊端,而且docker已经提供了容 ...
- nginx应用总结(1)-- 基础知识和应用配置梳理
在linux系统下使用nginx作为web应用服务,用来提升网站访问速度的经验已五年多了,今天在此对nginx的使用做一简单总结. 一.nginx服务简介Nginx是一个高性能的HTTP和反向代理服务 ...
- Linux运维笔记-日常操作命令总结(1)
在linux日常运维中,我们平时会用到很多常规的操作命令. 查看服务器的外网ip [root@redis-new01 ~]# curl ifconfig.me [root@redis-new01 ~] ...
- bootstrap面试题
1.你能描述一下渐进增强和优雅降级之间的不同吗? 优雅降级:Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会检查以确认它们是否能正常工作.由于IE独特的盒模型布局问题,针 ...
- 《Metasploit渗透测试魔鬼训练营》第一章读书笔记
第1章 魔鬼训练营--初识Metasploit 20135301 1.1 什么是渗透测试 1.1.1 渗透测试的起源与定义 如果大家对军事感兴趣,会知道各国军队每年都会组织一些军事演习来锻炼军队的攻防 ...
- [what is machine learning?]
1.2 [what is machine learning?] 1.人:observation --> learing --> skill 机器:data --> ML --& ...
- 第二次sprint
一.这次冲刺就是实施阶段了,主要对程序进行一次骨架的构建,基本上已经完成了界面的设计,但是在算法上还有很大漏洞,整个程序还是属于不成熟阶段. 二.这是我们的界面 三.已经有的功能:随机生成题目并算出答 ...
- SQL Server 递归查询上级或下级组织数据(上下级数据通用查询语法)
查询上级组织数据: WITH OCTE AS ( AS LVL FROM IOV_Users U LEFT JOIN IOV_Organization O ON U.OrgId=O.ID UNION ...
- windows下net命令失败
D:\apache-tomcat-7.0.57\bin>net start mysql57发生系统错误 5. 拒绝访问. 以管理员身份运行 run as administrator 打开cmd. ...