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 疫情控制 (二分,贪心,倍增)的更多相关文章

  1. luogu1084 [NOIp2012]疫情控制 (二分答案+倍增+dfs序)

    先二分出一个时间,把每个军队倍增往上跳到不能再跳 然后如果它能到1号点,就记下来它跳到1号点后剩余的时间:如果不能,就让它就地扎根,记一记它覆盖了哪些叶节点(我在这里用了dfs序+差分,其实直接dfs ...

  2. LUOGU P1084 疫情控制(二分+贪心+树上倍增)

    传送门 解题思路 比较神的一道题.首先发现是最小值问题,并且具有单调性,所以要考虑二分答案.其次有一个性质是军队越靠上越优,所以我们要将所有的军队尽量向上提,这一过程我们用倍增实现.发现这时有两种军队 ...

  3. NOIP2012疫情控制(二分答案+树上贪心)

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

  4. 洛谷P1084 疫情控制(贪心+倍增)

    这个题以前写过一遍,现在再来写,感觉以前感觉特别不好写的细节现在好些多了,还是有进步吧. 这个题的核心思想就是贪心+二分.因为要求最小时间,直接来求问题将会变得十分麻烦,但是如果转换为二分答案来判断可 ...

  5. Luogu P1084 [NOIP2012]疫情控制

    题目 首先我们二分一下答案. 然后我们用倍增让军队往上跳,最多先跳到根的子节点. 如果当前军队可以到达根节点,那么记录一下它的编号和它到达根节点后还可以走的时间. 并且我们记录根节点的叶子节点上到根节 ...

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

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

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

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

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

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

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

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

随机推荐

  1. php 中 opendir() readdir() scandir()

    opendir(path,context)若成功,则该函数返回一个目录流,否则返回 false 以及一个 error.可以通过在函数名前加上 “@” 来隐藏 error 的输出. readdir() ...

  2. 微信小程序中的组件

    前言 之前做小程序开发的时候,对于开发来说比较头疼的莫过于自定义组件了,当时官方对这方面的文档也只是寥寥几句,一笔带过而已,所以写起来真的是非常非常痛苦!! 好在微信小程序的库从 1.6.3 开始,官 ...

  3. 生产者消费者模式 php 【转】

    在工作中常常听到某某大牛之间的交谈会涉及到,xx消费者啊啥的,到底什么大牛之间讲的是什么? 这篇文章主要解决三个问题: 1.到底什么是生产者和消费者,以及它们之间的故事 2.它们之间靠什么交流 3.应 ...

  4. 12.9 Daily Scrum

    在一些实现上,开发人员提出了意见,经过讨论后,我们决定取消“推荐餐厅”的功能,增加了“菜谱分类”的功能. 同时更新了相关人员的任务.   Today's Task Tomorrow's Task 丁辛 ...

  5. 第六次Scrum meeting

    第六次Scrum  meeting 任务及完成度: 成员 12.21 12.22 陈谋 任务1040:完成stackoverflow的数据处理后的json处理(98%) 任务1114-1:完成对网页数 ...

  6. b总结

    Beta 答辩总结 评审表 组名 格式 内容 ppt 演讲 答辩 总计 天机组 15 15 13 15 14 72 PMS 16 16 15 16 16 79 日不落战队 16 17 17 17 17 ...

  7. ppm\℃是什么意思/

    转自http://www.zybang.com/question/b158a106b4e39d8fdb2b93fd3777a00f.html 在基准电压的数据手册里,我们会找到一个描述基准性能的直流参 ...

  8. C 实现选择排序

    一.选择排序的思想 假设有一个7元素的数组 [11, 24, 5, 17, 2, 8, 20],我们通过选择排序来从小到大排序. 思想是进行7次外循环从0-->6,每一次又是一个内循环,从i+1 ...

  9. CS、IP和PC寄存器

    CS寄存器和IP寄存器: 首先强调一下,这两个寄存器非常非常重要,CS的全拼为“Code segment”,即代码段寄存器,对应于内存中的存放代码的内存区域,用来存放内存代码段区域的入口地址(段基址) ...

  10. thread run 和 start 的区别

    run 方法 也可以调用线程启动   但是单线程(为顺序执行) 而start方法 启动的线程为多个线程之间争夺cpu的执行权(为随机的) 摘录于http://www.cnblogs.com/sunfl ...