运输计划|P2680
https://www.luogu.com.cn/problem/P2680
题目背景
公元 2044 年,人类进入了宇宙纪元。
题目描述
公元 2044 年,人类进入了宇宙纪元。
L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。
为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
输入格式
第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。
接下来 n-1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。
数据保证
接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。
输出格式
一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。
输入输出样例
输入 #1
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
输出 #1
11
说明/提示
所有测试数据的范围和特点如下表所示
请注意常数因子带来的程序效率上的影响。
对于 100\% 的数据,保证:1≤ai,bi≤n,0≤ti≤1000,1≤ui,vi≤n。
江江强~
正题
抽象题意:一棵有边权的树有n个节点
给出m条路径的起点和终点
你可以任意将一条边的边权变成0
求操作后m条边中所有路径最小权值和的最大值最小是多少
这TM原题不比这说的清楚。。。
但抽象完了后就能想到二分答案力
check怎么做呢?
如果已知路径有边权和 比 二分到的答案大的
if(edge_sum[i]>mid)
那必须删掉这条路径上的一条边。
这种路径有很多,怎么办?
只能删这些路径的交集了!
补充一下交集怎么求,十分滴简单(
只要差分维护区间和,把路径上的边++,最后看哪条边==路径条数就可以了
。。。
只要交集中有一个可以让最长的路径都降到mid一下,那就return true;
//最后吹一下aqx二分
int now=0;//下界
for(int step=(1<<20)/*区间大小*/;step>0;step>>=1){
if(check(now+step))now+=step;
}
//妈的难死了
//二分答案
//假设二分到的答案以上的都被薄纱了
//那就枚举一遍,O(m)枚举,没办法,只剩O(1)check
// /kk
//O(1)check怎么check
//怎么O(n)以内判断能不能
//check()
/*
首先选的边要大于最大值减去二分值
如何确定这条边在每个的最短路径上
江江强
江江还是强
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
//重构代码
//我要读入一棵前缀树,怎么办
//蚌
//得存边权 /kk
const int MAXN=3e5+10;
struct tree{//前项星罢了
int to;
int w;
int next;
}edge[MAXN*2];
int head[MAXN];
int _cnt=0;
void add(int u,int v,int w){
edge[++_cnt].to=v;
edge[_cnt].w=w;
edge[_cnt].next=head[u];
head[u]=_cnt;
}
int n,m;
//打个快读
inline int read(){
int s=0,f=1;
char c=getchar();
while(c<48 || c>57){if(c=='-')f=-f;c=getchar();}
while(c>=48 && c<=57)s=s*10+c-48,c=getchar();
return s*f;
}
int pretree[MAXN];
int d[MAXN][20];
int depth[MAXN];
void predfs(int x,int dep){//要算根到节点的路径和
depth[x]=dep;
for(int i=head[x];i!=0;i=edge[i].next){
int nxt=edge[i].to;
if(depth[nxt])continue;
int w=edge[i].w;
pretree[nxt]=pretree[x]+w;
d[nxt][0]=x;
for(int i=1;i<20;i++){//还要预处理LCA
d[nxt][i]=d[d[nxt][i-1]][i-1];
}
predfs(nxt,dep+1);
}
}
//还是 亲 爱 的 LCA
int LCA(int u,int v){
if(depth[u]<depth[v]){
swap(u,v);
}
for(int i=19;i>=0;i--){
if(depth[d[u][i]]>=depth[v]){
u=d[u][i];
}
}
if(u==v)return u;
for(int i=19;i>=0;i--){
if(d[u][i]!=d[v][i]){
u=d[u][i];
v=d[v][i];
}
}
return d[u][0];
}
struct node{
int s;
int t;
int lca;
int length;
friend bool operator <(const node &a,const node &b){
return a.length<b.length;
}
}ship[MAXN];//从0开始
//大毒瘤来喽
int cnttree[MAXN];
//dfs子树和
//打ABC去了,鸽了鸽了
//开打
void dfs(int x){
//我前缀都不会写 /kk
//随便写一个试试
for(int i=head[x];i!=0;i=edge[i].next){
int nxt=edge[i].to;
if(nxt==d[x][0])continue;
dfs(nxt);
cnttree[x]+=cnttree[nxt];
}
return ;
}
//「难蚌的check」|二分的核心
bool check(int x){
memset(cnttree,0,sizeof(cnttree));
//还得差分
//先得看哪些要差
//aqx二分!
//不对lower_bound够了
int pos=upper_bound(ship,ship+m,(node){0,0,0,x})-ship;//mid以上都要干掉,因为mid要留下,所以要严格大于mid
if(pos==m)return true;//全保留就不算了
int left=m-pos;//就剩这点点
int target=ship[m-1].length-x;
for(int i=pos;i<m;i++){//左闭右开
//上边!
cnttree[ship[i].s]++;
cnttree[ship[i].t]++;
cnttree[ship[i].lca]-=2;
}
dfs(1);
for(int i=1;i<=n;i++){
if(cnttree[i]==left && pretree[i]-pretree[d[i][0]]>=target)return true;
}
return false;
}
signed main(){
n=read(),m=read();
int u,v,w;
for(int i=1;i<n;i++){
u=read(),v=read(),w=read();
add(u,v,w);
add(v,u,w);
}
predfs(1,1);
for(int i=0;i<m;i++){
u=read();
v=read();
//妈的不想打lca /fn/fn!
int lca=LCA(u,v);
ship[i]=(node){u,v,lca,pretree[u]+pretree[v]-2*pretree[lca]};
}
sort(ship,ship+m);
//突然没思路了 o(0_0')o
//等等我想想排序的细节
//就是,咋做来着
//二分的时候
//再二分一次找到哪些边的length比mid长
//——这就是要干掉的边
//O(m)处理每条路径的差分加减
//O(n)dfs一遍得到原数组
//O(n)处理每条==cnt的边,要是边长>=最长边-mid就是true
//问题不大
//二分喽~
//aqx 二分!
int now=0;//问的是删一条边 最长 路径的 最 小 值
if(check(0)){//特判注意!!!
cout<<'0';//当然还有奇迹淫巧 : now=-1初值
return 0;
}
for(int i=(1<<29);i>0;i>>=1){
if(!check(i+now)){//最后一个不行的
now+=i;
}
}
cout<<now+1;
return 0;//完结撒花!~
}