Luogu 2680 NOIP 2015 运输计划(树链剖分,LCA,树状数组,树的重心,二分,差分)
Luogu 2680 NOIP 2015 运输计划(树链剖分,LCA,树状数组,树的重心,二分,差分)
Description
L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物
流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰。
为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后, 这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的 物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞,试求出小 P 的物流公司完成阶段 性工作所需要的最短时间是多少?
Input
第一行包括两个正整数 n、m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。
接下来 n-1 行描述航道的建设情况,其中第 i 行包含三个整数 ai, bi 和 ti,表示第
i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。
接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j个 运输计划是从 uj 号星球飞往 vj 号星球。
Output
输出 共1行,包含1个整数,表示小P的物流公司完成阶段性工作所需要的最短时间。
Sample Input
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
Sample Output
11
Http
Luogu:https://www.luogu.org/problem/show?pid=2680
Source
树链剖分,最近公共祖先LCA,树的重心,二分,差分,树状数组
题目大意
给出一棵边权树,现在给定若干对点,要求选择一条树边使其权值为0,使得所有的点对的距离的最大值最小。
解决思路
基本的思路是先通过求最近公共祖先LCA来求得这若干对点对的距离。按照距离从大到小排序后,二分最大的距离\(mid\),将那些距离大于\(mid\)的连接点对的路径打上标记,设有k个这样的路径。找出那些这k条路径都覆盖的边,判断如果把这条边距离设为0是否可以使得所有的路径长度都小于\(mid\),如果可以,则说明该\(mid\)是可行的,否则不行。
但单纯的照上面这样做是不行的,我们考虑优化。
首先是求这若干组点对的距离。我们知道在树上求两点之间的距离与LCA有关,而求LCA有两种算法,一种是倍增法,另一种是树链剖分。这里为了方面后面的差分,同时也是为了减小常数,选用树链剖分的方法。我们将树按照轻重剖分成轻链和重链,将树上的问题转换成若干区间的问题。
接下来考虑如何维护区间。由题可知,本题需要维护的是路径长度,也就是若干边权之和。但对于边权的操作远不如点权方便,所以我们把边权转化成为点权,具体方法是将点\(u\)到其父亲节点\(f\)的边的权转化成为点\(u\)的点权。这里需要注意的是,将边权转化成为点权之后,两个点\(u,v\)之间的路径长度就不是\(u\)到\(lca(u,v)\)的点权和加\(v\)到\(lca(u,v)\)的点权和了,因为\(lca(u,v)\)的点权是到其父亲的边权,不在\(u\)到\(v\)的路径上,所以在向上跳转的时候要跳到\(lca\)的深度+1的点。同样需要改变的也包括区间查询和差分。
至于维护这个和,因为只有查询和插入,所以这里考虑使用常数更小的树状数组来实现。
现在考虑将选出的长度大于\(mid\)的路径标记。可以将路径上的每一条边标记+1,然后可以知道,如果有k条这样的路径,那么标记为k的边就是这k条路径都覆盖的。但是暴力标记是不行的。我们发现每次都是将k条路径都标记完后再操作,所以我们可以考虑差分。而又因为我们用树链剖分将树划分成链,所以可以参考求解LCA的在链上跳转的方式,在重链的top位置++,而在当前位置+1的地方--,注意,这些位置都是在点的新编号的位置(也就是在点在树状数组中的编号),因为这样就变成区间差分了。但是最后一条链的起始位置(即lca的位置)要+1,具体原因是我们把边权转化成了点权,而lca的点权代表的边权不在路径上。
最后来判断是否可行。因为最长路径一定会被选,所以我们在从\(1~n\)累加差分数组,当累加器\(ret==k\)时,判断最长路径减去当前点\(i\)对应的点权是否小于当前二分的\(mid\),如果存在一条这样的边,则可行,否则不行。
另外,我们知道在树上操作的效率一定程度上取决于对根节点的选取,所以我们可以在最开始求出树的重心,并以重心为根,这样可以提高效率。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define lowbit(x) ((x)&(-x))
const int maxN=300101;
const int maxM=300101;
const int inf=2147483647;
int n;
class Edge//边
{
public:
int u,v,w;
};
class TreeArr//树状数组
{
public:
int A[maxN];
TreeArr()
{
memset(A,0,sizeof(A));
return;
}
void Add(int pos,int x)//插入
{
while (pos<=n)
{
A[pos]=A[pos]+x;
pos=pos+lowbit(pos);
}
return;
}
int Sum(int x)//求和1~x
{
int ret=0;
while (x>0)
{
ret=ret+A[x];
x=x-lowbit(x);
}
return ret;
}
int query(int l,int r)//求区间和
{
return Sum(r)-Sum(l-1);
}
};
class Question//对于每一条路径
{
public:
int u,v,w;
};
bool operator < (Question A,Question B)//按照路径长度降序排序
{
return A.w>B.w;
}
int qus;
int root,rootsize;//重心,以及对应的最大子树-最小子树
int longestpath=0;//最长路径
//Graph 原来树中的data
int cnt=0;
int Head[maxN];
int Next[maxN*2];
Edge E[maxN*2];
int Node_W[maxN];//边权转化后的点权
//Tree_Chain 树链剖分维护的data
int Depth[maxN];//深度
int Father[maxN];//父节点编号
int Top[maxN];//链顶
int Size[maxN];//大小
int HeavySon[maxN];//重儿子
int Id[maxN];//Id[i]表示树状数组中i对应的原节点编号。若Id[i]==j则dfn[j]==i
int dfn[maxN];//原节点i对应的在树链剖分中分配的新编号
//TreeArr
TreeArr TA;//树状数组
//Q
Question Q[maxM];//点对和路径
//Insert
int Num[maxN];//差分数组
int read();
void Add_Edge(int u,int v,int w);
void dfs(int u,int father);//求重心
void dfs1(int u,int father);//树链剖分第一遍dfs
void dfs2(int u,int nowTop);//树链剖分第二遍dfs
int Calc_Path(int u,int v);//计算u到v的路径长度
bool check(int nowmid);//检查当前最大距离是否合法
void Insert(int u,int v);//将u到v的路径差分标记
int main()
{
memset(Head,-1,sizeof(Head));
rootsize=inf;
n=read();
qus=read();
for (int i=1;i<n;i++)//读入边
{
int u,v,w;
u=read();
v=read();
w=read();
Add_Edge(u,v,w);
Add_Edge(v,u,w);
}
//处理出重心
dfs(1,1);
//树链剖分
Depth[1]=1;
dfs1(root,root);
cnt=0;
dfs2(root,root);
for (int i=1;i<=n;i++)//将边权转化成点权并存入树状数组
TA.Add(dfn[i],Node_W[i]);
//求解路径
for (int i=1;i<=qus;i++)
{
Q[i].u=read();
Q[i].v=read();
Q[i].w=Calc_Path(Q[i].u,Q[i].v);
longestpath=max(Q[i].w,longestpath);//选出最长路径
//cout<<Q[i].w<<endl;
}
sort(&Q[1],&Q[qus+1]);//将路径排序
int l=1,r=longestpath;//二分
int Ans;
do
{
int mid=(l+r)/2;
if (check(mid))//检查
{
Ans=mid;
r=mid-1;
}
else
l=mid+1;
}
while (l<=r);
printf("%d\n",Ans);
return 0;
}
int read()
{
int x=0;
char ch=getchar();
while ((ch>'9')||(ch<'0'))
ch=getchar();
while ((ch<='9')&&(ch>='0'))
{
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;
return;
}
void dfs(int u,int father)//求重心
{
Size[u]=1;
int mx=0,mn=inf;//分别记录最大子树和最小子树
for (int i=Head[u];i!=-1;i=Next[i])
{
int v=E[i].v;
if (v==father)
continue;
dfs(v,u);
Size[u]+=Size[v];
if (Size[v]>mx)
mx=Size[v];
else
if (Size[v]<mn)
mn=Size[v];
}
if (n-Size[u]>mx)
mx=n-Size[u];
else
if (n-Size[u]<mn)
mn=n-Size[u];
if ((mx!=0)&&(mn!=inf)&&(mx-mn<rootsize))
{
root=u;
rootsize=mx-mn;
}
return;
}
void dfs1(int u,int father)//树链剖分第一次dfs
{
Size[u]=1;
HeavySon[u]=0;
for (int i=Head[u];i!=-1;i=Next[i])
{
int v=E[i].v;
if (v==father)
continue;
Father[v]=u;
Depth[v]=Depth[u]+1;
Node_W[v]=E[i].w;//将边权转换成点权
dfs1(v,u);
Size[u]=Size[u]+Size[v];
if ((HeavySon[u]==0)||(Size[HeavySon[u]]<Size[v]))
HeavySon[u]=v;
}
return;
}
void dfs2(int u,int nowTop)//树链剖分第二次dfs
{
cnt++;
dfn[u]=cnt;
Id[cnt]=u;
Top[u]=nowTop;
if (HeavySon[u]==0)
return;
dfs2(HeavySon[u],nowTop);
for (int i=Head[u];i!=-1;i=Next[i])
if ((E[i].v!=Father[u])&&(E[i].v!=HeavySon[u]))
dfs2(E[i].v,E[i].v);
return;
}
int Calc_Path(int u,int v)//计算u到v的路径长
{
int path=0;
while (Top[u]!=Top[v])
{
if (Depth[Top[u]]<Depth[Top[v]])
swap(u,v);
path+=TA.query(dfn[Top[u]],dfn[u]);
u=Father[Top[u]];
}
if (Depth[u]>Depth[v])
swap(u,v);
return path+TA.query(dfn[u]+1,dfn[v]);
}
bool check(int nowmid)//检查合法性
{
int pos=0;
while (Q[pos+1].w>nowmid)//将路径长度大于mid的路径加入差分
{
pos++;
Insert(Q[pos].u,Q[pos].v);
}
int now=0;
for (int i=1;i<=n;i++)
{
now=now+Num[i];
Num[i]=0;
if ((now==pos)&&(longestpath-Node_W[Id[i]]<=nowmid))//当当前第i个点被所有pos条不满足的路径都覆盖时,若最长路径减去其点权比nowmid小,说明能够满足
{
for (int j=i+1;j<=n;j++)
Num[j]=0;
return 1;
}
}
return 0;
}
void Insert(int u,int v)//差分
{
while (Top[u]!=Top[v])
{
if (Depth[Top[u]]<Depth[Top[v]])
swap(u,v);
Num[dfn[Top[u]]]++;
Num[dfn[u]+1]--;//注意这里+1
u=Father[Top[u]];
}
if (Depth[u]>Depth[v])
swap(u,v);
Num[dfn[u]+1]++;//常规的差分这里是不要+1的,但这里需要,因为dfn[u]所对应的边不在u到v的路径上
Num[dfn[v]+1]--;
return;
}
Luogu 2680 NOIP 2015 运输计划(树链剖分,LCA,树状数组,树的重心,二分,差分)的更多相关文章
- cogs 2109. [NOIP 2015] 运输计划 提高组Day2T3 树链剖分求LCA 二分答案 差分
2109. [NOIP 2015] 运输计划 ★★★☆ 输入文件:transport.in 输出文件:transport.out 简单对比时间限制:3 s 内存限制:256 MB [题 ...
- 4632 NOIP[2015] 运输计划
4632 NOIP[2015] 运输计划 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 大师 Master 题解 题目描述 Description 公元 2044 ...
- [NOIP 2015]运输计划-[树上差分+二分答案]-解题报告
[NOIP 2015]运输计划 题面: A[NOIP2015 Day2]运输计划 时间限制 : 20000 MS 空间限制 : 262144 KB 问题描述 公元 2044 年,人类进入了宇宙纪元. ...
- Qtree3题解(树链剖分(伪)+线段树+set)
外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意: 很明显吧.. 题解: 我的做法十分的暴力:树链剖分(伪)+线段树+\(set\)... ...
- 【算法学习】【洛谷】树链剖分 & P3384 【模板】树链剖分 P2146 软件包管理器
刚学的好玩算法,AC2题,非常开心. 其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用. 更重要的是我很好调试,两题都是几乎一遍过的. 介绍树链剖分前,先确保已经学会以下基本技巧: ...
- 【bzoj4999】This Problem Is Too Simple! 树链剖分+动态开点线段树
题目描述 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x<2^31) ...
- NOIP 2015运输计划
题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家 ...
- 刷题总结——骑士的旅行(bzoj4336 树链剖分套权值线段树)
题目: Description 在一片古老的土地上,有一个繁荣的文明. 这片大地几乎被森林覆盖,有N座城坐落其中.巧合的是,这N座城由恰好N-1条双 向道路连接起来,使得任意两座城都是连通的.也就是说 ...
- [bzoj 3531][SDOI2014]旅行(树链剖分+动态开点线段树)
题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3531 分析: 对于每个颜色(颜色<=10^5)都建立一颗线段树 什么!那么不是M ...
随机推荐
- springboot+thymeleaf刨坑——首页加载js/css等失败解决方法
在使用thymeleaf加载css或js样式,当我们进入登录页的时候发现,所有的样式都是加载失败的.原因是在新版中有这样一个坑……: 当我们设置了addInterceptors-注册拦截器的时候,通常 ...
- 《Linux内核设计与实现》读书笔记三
Chapter 18 调 试 18.1 准备开始 1.准备工作: 一个bug 一个藏匿bug的内核版本 相关内核代码的知识和运气 2.执行foo就会让程序立即产生核心信息转储(dump core). ...
- 软件工程导论课后习题Github作业(把一个英文句子中的单词次序逆序,单词中字母正常排列)
Java源代码 package yly; import java.util.Scanner; public class ruanjian { public static void main(St ...
- Maximal Binary Matrix CodeForces - 803A (贪心+实现)
题目链接 题意有点坑: 给你一个N*N的矩阵,让你填入K个1,使之整个矩阵关于左上到右下的对角线对称,并且这个要求这个矩阵的字典序最大. 对矩阵的字典序的定义是从每一行的第一个元素开始比较,大着为字典 ...
- NODE中解决跨域请求的问题
1.Node Express 解决请求跨域请求 标签(空格分隔): 跨域 1是Access-Control-Allow-Origin 允许的域 2是Access-Control-Allow-Heade ...
- [日常工作]WorkStation 使用端口转发的方式使用宿主机IP地址提供服务
1. 虚拟机内的地址如果经常变化,或者是想使用宿主机进行网络服务 但是又不想有人能够访问具体的服务器提供机器.. 可以使用宿主机转发虚拟机的端口的方式来进行处理. workstation 比较好实现 ...
- 转《js闭包与内存泄漏》
首先,能导致内存泄漏的一定是引用类型的变量,比如函数和其他自定义对象.而值类型的变量是不存在内存泄漏的,比如字符串.数字.布尔值等.因为值类型是靠复制来传递的,而引用类型是靠类似c语言中的指针来传递的 ...
- MySQL下载安装详情教程
1.下载MySQL数据库可以访问官方网站:https://www.mysql.com/ 2.点击DOWNLOADS模块下的Community模块下的MySQL Community Server进行下载 ...
- 在Python中调用C++模块
一.一般调用流程 http://www.cnblogs.com/huangshujia/p/4394276.html 二.Python读取图像并传入C++函数,再从C++返回结果图像给Python h ...
- appium学习记录1
xpath定位: 语法 driver.find_element_by_xpath("//android.widget.EditText[@index="登陆"/../pr ...