题目描述

公元 \(2044\) 年,人类进入了宇宙纪元。

\(L\) 国有 \(n\) 个星球,还有 \(n-1\) 条双向航道,每条航道建立在两个星球之间,这 \(n-1\) 条航道连通了 \(L\) 国的所有星球。

小 \(P\) 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 \(u_i\) 号星球沿最快的宇航路径飞行到 $v_i$ 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 $j$,任意飞船驶过它所花费的时间为 $t_j$,并且任意两艘飞船之间不会产生任何干扰。

为了鼓励科技创新, $L$ 国国王同意小 $P$ 的物流公司参与 $L$ 国的航道建设,即允许小$P$ 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

在虫洞的建设完成前小 $P$ 的物流公司就预接了 $m$ 个运输计划。在虫洞建设完成后,这 $m$ 个运输计划会同时开始,所有飞船一起出发。当这 $m$ 个运输计划都完成时,小 $P$ 的物流公司的阶段性工作就完成了。

如果小 $P$ 可以自由选择将哪一条航道改造成虫洞, 试求出小 $P$ 的物流公司完成阶段性工作所需要的最短时间是多少?

输入格式

第一行包括两个正整数 $n,m$,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 $1$ 到 $n$ 编号。

接下来 $n-1$ 行描述航道的建设情况,其中第 $i$ 行包含三个整数 $a_i,b_i$ 和 $t_i$,表示第 $i$ 条双向航道修建在 $a_i$ 与 $b_i$ 两个星球之间,任意飞船驶过它所花费的时间为 $t_i$。数据保证 $1 \leq a_i,b_i \leq n$且 $0 \leq t_i \leq 1000$。

接下来 $m$ 行描述运输计划的情况,其中第 $j$ 行包含两个正整数 $u_j$ 和$v_j$,表示第 $j$ 个运输计划是从 $u_j$ 号星球飞往 $v_j$号星球。数据保证 $1 \leq u_i,v_i \leq n$

输出格式

输出文件只包含一个整数,表示小 $P$ 的物流公司完成阶段性工作所需要的最短时间。

  还记得当初在$noip$考场上,不会树剖不会二分答案,于是对于这道题就是狂跑lca啊lca……

  后来学了各种东西之后,就自己打了一个复杂度为$O(n \log ^2 n)$的算法,大意如下:

  先对于整棵树进行树链剖分,然后考虑二分一个答案(因为题目所求是最大值最小,所以答案单调),只需判断这个答案是否可行。于是我们需要把长度大于$x$的路径扫一遍,求一下这些路径的交,从交中找出一条权值最大的边,把这条边权值变为$0$(显然这样最优而且并不需要真的赋值为$0$),判断一下最长路径现在是否小于等于$x$即可。

  接着,发现被卡了。在UOJ上只有97分。于是,接下来就是奇技淫巧时间。

  有一次有点无聊,于是把树链剖分中的求重儿子部分的代码中的小于号改为了小于等于号,就这么AC了……汗……

  下面是代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 2147483647
#define maxn 300001 using namespace std; struct data{
int f,t,v;
}he[maxn];
int fa[maxn],head[maxn*2],to[maxn*2],next[maxn*2],c[maxn*2];
int dep[maxn],son[maxn],top[maxn],siz[maxn],w[maxn],zui;
int tc[maxn],fc1[maxn],fc[maxn],cha[maxn+1],tt,m,n,x,y,l,r; int getint(){
int w=0,q=0;
char c=getchar();
while((c>'9'||c<'0')&&c!='-') c=getchar();
if(c=='-') q=1,c=getchar();
while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
return q?-w:w;
} void dfs1(int u,int dd){
dep[u]=dd;siz[u]++;
for(int i=head[u];i;i=next[i])
if(!dep[to[i]]){
dfs1(to[i],dd+1);siz[u]+=siz[to[i]];
fa[to[i]]=u;fc[to[i]]=c[i];
if(siz[to[i]]>=siz[son[u]]) son[u]=to[i];
}
} void dfs2(int u,int dd){
w[u]=++tt;top[u]=dd;
fc1[tt]=fc[u];
if(son[u]) tc[son[u]]=tc[u]+fc[son[u]],dfs2(son[u],dd);
for(int i=head[u];i;i=next[i])
if(to[i]!=son[u]&&to[i]!=fa[u])
dfs2(to[i],to[i]);
} inline int lca(int x,int y){
int ju=0;
while(top[x]!=top[y]){
int a=fa[top[x]],b=fa[top[y]];
if(dep[a]<dep[b]) swap(x,y),swap(a,b);
cha[w[x]+1]--;cha[w[top[x]]]++;
ju+=tc[x]+fc[top[x]];x=a;
}
if(dep[x]<dep[y]) swap(x,y);
cha[w[x]+1]--;cha[w[son[y]]]++;
ju+=tc[x]-tc[y];
return ju;
} inline bool pd(int kk){
int ci=0;
memset(cha,0,sizeof(cha));
for(register int i=1;i<=m;i++)
if(he[i].v>kk) lca(he[i].f,he[i].t),ci++;
int hehe=0,hh=c[0];
for(register int i=1;i<=tt;i++){
hh+=cha[i];
if(hh==ci) hehe=max(hehe,fc1[i]);
}
if(zui-hehe<=kk) return 1;
return 0;
} int main(){
n=getint();m=getint();
for(int i=1;i<n;i++){
x=getint();y=getint();
to[++tt]=y,next[tt]=head[x],head[x]=tt;
to[++tt]=x,next[tt]=head[y],head[y]=tt;
c[tt-1]=c[tt]=getint();
}
dfs1(1,1);tt=0;dfs2(1,1);
for(int i=1;i<=m;i++){
he[i].f=getint();he[i].t=getint();
he[i].v=lca(he[i].f,he[i].t);
r=max(r,he[i].v);
}
zui=r++;
while(l!=r){
int mid=(l+r)>>1;
if(pd(mid))r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}

  但是后来,我发现其实这个算法是可以优化到$O(n \log n)$的。设当前二分的答案为$x$,由于每一次最长路径长度小于等于$x$时显然可以,否则不满足的所有路径的交必定在最长路径上(交为空时其实也是最长路径的子集),而且是连续的一段。然后我们把最长链抠出来,对于其他每一条路径与最长路径求一下交,那么路径的交就变成区间上的问题了,可以线性地做。于是复杂度降为$O(n \log n)$。

  还有怎么两条路径求交的问题。由于我们只需求出两条路径交的两个端点,可以把这两条路径的共4个点lca一下,分类讨论即可。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 300010 using namespace std;
typedef long long llg; struct data{
int u,v,g,x;
}s[maxn];
int n,m,dep[maxn],son[maxn],siz[maxn],fa[maxn],tc[maxn],top[maxn],fc[maxn];
int head[maxn],to[maxn<<1],next[maxn<<1],c[maxn<<1],tt,K,nn,now;
int dc[maxn],cd[maxn],ld,b[maxn],lb,bb[maxn],ca[maxn]; int getint(){
int w=0;bool q=0;
char c=getchar();
while((c>'9'||c<'0')&&c!='-') c=getchar();
if(c=='-') c=getchar(),q=1;
while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
return q?-w:w;
} void link(int x,int y){
to[++tt]=y;next[tt]=head[x];head[x]=tt;
to[++tt]=x,next[tt]=head[y];head[y]=tt;
c[tt]=c[tt-1]=getint();
} void dfs(int u){
siz[u]=1;
for(int i=head[u],v;v=to[i],i;i=next[i])
if(!siz[v]){
dep[v]=dep[u]+1; fa[v]=u; fc[v]=c[i];
dfs(v); siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
} void dfs(int u,int d){
top[u]=d;
if(son[u]) tc[son[u]]=tc[u]+fc[son[u]],dfs(son[u],d);
for(int i=head[u],v;v=to[i],i;i=next[i])
if(!top[v]) dfs(v,v);
} inline int lca(int u,int v){//树链剖分求lca
now=0;
while(top[u]!=top[v]){
if(dep[fa[top[u]]]<dep[fa[top[v]]]) swap(u,v);
now+=tc[u]+fc[top[u]]; u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
now+=tc[v]-tc[u]; return u;
} void kou(int u,int v,int g){//把最长路径抠出来
while(u!=g) cd[++ld]=u,ca[ld]=fc[u],u=fa[u]; cd[++ld]=g; //从u到g的路径
while(v!=g) b[++lb]=v,bb[lb]=fc[v],v=fa[v]; //从v到g的路径
while(lb) ca[ld]=fc[b[lb]],cd[++ld]=b[lb--]; //ca表示长度
for(int i=1;i<=ld;i++) dc[cd[i]]=i;
} inline bool pd(int x){//判断是否可行
int l=1,r=ld,gi=0;
for(register int i=1;i<=m;i++)
if(s[i].x>x){
l=max(l,s[i].u);
r=min(r,s[i].v);
}
for(int i=l;i<r;i++) gi=max(gi,ca[i]);
return nn-gi<=x;
} inline void gi(int &xx,int u){
int x,y;
x=lca(u,s[K].u); if(dep[x]>dep[s[K].g]){xx=dc[x];return;}
y=lca(u,s[K].v); if(dep[y]>dep[s[K].g]){xx=dc[y];return;}
xx=dc[s[K].g];//点在路径之外,要么另一个点的两个lca在最长路径上,此时视为最长路径的最上点,要么两条路径交为空
} int main(){
n=getint(); m=getint();
for(int i=1;i<n;i++) link(getint(),getint());
dep[1]=1;dfs(1); dfs(1,1);
for(register int i=1;i<=m;i++){
s[i].u=getint(); s[i].v=getint();
s[i].g=lca(s[i].u,s[i].v); s[i].x=now;
if(now>nn) nn=now,K=i;//nn为最长路径长度,K为最长路径标号
}
kou(s[K].u,s[K].v,s[K].g);
for(register int i=1,l,r;i<=m;i++)
if(i!=K){
gi(l,s[i].u); gi(r,s[i].v);//lca分类讨论求路径交
if(l>r) swap(l,r);
s[i].u=l,s[i].v=r;//注意这里的区间为[l,r)
}
s[K].u=1,s[K].v=ld;
int l=0,r=nn,mid;
while(l!=r){
mid=l+r>>1;
if(pd(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
}

  又或者可以直接优化查分,使得差分变为$O(1)$,具体思路如下:把一条路径上的边全部加$1$,那么可以把两个端点的值加$1$,lca的值减$2$,全部完成后dfs一遍解决。但不知道为什么,速度比第一种算法还要慢,大概是常数有点大吧……

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 300010 using namespace std;
typedef long long llg; struct data{
int u,v,g,x;
}s[maxn];
int n,m,dep[maxn],son[maxn],siz[maxn],fa[maxn],tc[maxn],top[maxn],fc[maxn];
int head[maxn],to[maxn<<1],next[maxn<<1],c[maxn<<1],tt,now,nn;
int ch[maxn],ci,_m; int getint(){
int w=0;bool q=0;
char c=getchar();
while((c>'9'||c<'0')&&c!='-') c=getchar();
if(c=='-') c=getchar(),q=1;
while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
return q?-w:w;
} void link(int x,int y){
to[++tt]=y;next[tt]=head[x];head[x]=tt;
to[++tt]=x,next[tt]=head[y];head[y]=tt;
c[tt]=c[tt-1]=getint();
} void dfs(int u){
siz[u]=1;
for(int i=head[u],v;v=to[i],i;i=next[i])
if(!siz[v]){
dep[v]=dep[u]+1; fa[v]=u; fc[v]=c[i];
dfs(v); siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
} void dfs(int u,int d){
top[u]=d;
if(son[u]) tc[son[u]]=tc[u]+fc[son[u]],dfs(son[u],d);
for(int i=head[u],v;v=to[i],i;i=next[i])
if(!top[v]) dfs(v,v);
} int lca(int u,int v){
now=0;
while(top[u]!=top[v]){
if(dep[fa[top[u]]]<dep[fa[top[v]]]) swap(u,v);
now+=tc[u]+fc[top[u]]; u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
now+=tc[v]-tc[u]; return u;
} void work(int u){
for(int i=head[u],v;v=to[i],i;i=next[i])
if(v!=fa[u])
work(v),ch[u]+=ch[v],ch[v]=0;
if(ch[u]==ci) _m=max(_m,fc[u]);
} bool pd(int x){
ci=_m=ch[1]=0;
for(int i=1;i<=m;i++)
if(s[i].x>x){
ch[s[i].u]++;ch[s[i].v]++;
ch[s[i].g]-=2; ci++;
}
work(1);
return nn-_m<=x;
} int main(){
File("a");
n=getint(); m=getint();
for(int i=1;i<n;i++) link(getint(),getint());
dep[1]=1;dfs(1); dfs(1,1);
for(int i=1;i<=m;i++){
s[i].u=getint(); s[i].v=getint();
s[i].g=lca(s[i].u,s[i].v);
nn=max(nn,s[i].x=now);
}
int l=0,r=nn,mid;
while(l!=r){
mid=l+r>>1;
if(pd(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
}

  不过这道题的标解的复杂度好像是并查集复杂度……留坑待填……

UOJ #150 【NOIP2015】 运输计划的更多相关文章

  1. [BZOJ4326][codevs4632][codevs5440][UOJ#150][NOIP2015]运输计划

    [BZOJ4326][codevs4632][codevs5440][UOJ#150][NOIP2015]运输计划 试题描述 公元 2044 年,人类进入了宇宙纪元. L 国有 n 个星球,还有 n− ...

  2. bzoj 4326: NOIP2015 运输计划

    4326: NOIP2015 运输计划 Time Limit: 30 Sec Memory Limit: 128 MB Description 公元 2044 年,人类进入了宇宙纪元.L 国有 n 个 ...

  3. NOIP2015 运输计划(bzoj4326)

    4326: NOIP2015 运输计划 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 886  Solved: 574[Submit][Status] ...

  4. [NOIP2015]运输计划 D2 T3 LCA+二分答案+差分数组

    [NOIP2015]运输计划 D2 T3 Description 公元2044年,人类进入了宇宙纪元. L国有n个星球,还有n-1条双向航道,每条航道建立在两个星球之间,这n-1条航道连通了L国的所有 ...

  5. NOIP2015 运输计划(二分+LCA+差分)

    4326: NOIP2015 运输计划 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 308  Solved: 208[Submit][Status] ...

  6. 数据结构(树链剖分):COGS 2109. [NOIP2015] 运输计划

    2109. [NOIP2015] 运输计划 ★★★   输入文件:transport.in   输出文件:transport.out   简单对比时间限制:1 s   内存限制:256 MB [题目描 ...

  7. BZOJ 4326 NOIP2015 运输计划 (二分+树上差分)

    4326: NOIP2015 运输计划 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1930  Solved: 1231[Submit][Statu ...

  8. cogs2109 [NOIP2015] 运输计划

    cogs2109 [NOIP2015] 运输计划 二分答案+树上差分. STO链剖巨佬们我不会(太虚伪了吧 首先二分一个答案,下界为0,上界为max{路径长度}. 然后判断一个答案是否可行,这里用到树 ...

  9. LOJ2425 NOIP2015 运输计划 【二分+LCA+树上差分】*

    LOJ2425 NOIP2015 运输计划 LINK 题意:给你一颗树,可以将任意一条边的权值变成0,然后求m条路径的长度的最小值 思路: 先二分最后的距离ans,然后我们把路程大于ans的所有路径拿 ...

  10. AC日记——[NOIP2015]运输计划 cogs 2109

    [NOIP2015] 运输计划 思路: 树剖+二分: 代码: #include <cstdio> #include <cstring> #include <iostrea ...

随机推荐

  1. Xcode cannot launch because the device is locked.

    When you plug in your iPhone, it will ask you to trust the computer. If you already trust and unlock ...

  2. Charles中如何对https抓包

    前言:下面介绍关于Charles中如何对https抓包 1.在默认没有相关设置HTTPS需要设置相关操作的时候,会出现下面的情况: 2.下面就是设置SSL Proxying,然后443是默可用的端口 ...

  3. javaScript基础语法(下)

    4.逻辑控制语句 4.1条件结构 条件结构分为if结构和switch结构. 通常在写代码时,您总是需要为不同的决定来执行不同的动作.您可以在代码中使用条件语句来完成该任务. 在 JavaScript ...

  4. 转:无法向会话状态服务器发出会话状态请求请。确保 ASP.NET State Service (ASP.NET 状态服务)已启动

    今天看到一篇文章感觉不错,收藏转载下. 原文地址:http://blog.csdn.net/sntyy/article/details/2090347 版权为原作者所有 无法向会话状态服务器发出会话状 ...

  5. 面试问题4:C语言预处理包括哪些

    问题描述:C语言 预处理包括哪些操作 C语言的三种预处理包括:宏定义(#define).文件包含(#include).条件编译(#if.#else.#endif). 对于宏定义的介绍: 宏定义必须写在 ...

  6. JavaScript Patterns 6.3 Klass

    Commonalities • There’s a convention on how to name a method, which is to be considered the construc ...

  7. Linux SendMail发送邮件失败诊断案例(三)

    一Linux服务器突然发送不出邮件,检查了很多地方都没有发现异常,检查/var/log/maillog发现如下具体信息: Apr 12 00:36:04 mylinux sendmail[4685]: ...

  8. javascript-适配器模式

    适配器模式笔记 将一个类(对象)的接口(方法或属性)转化成另一个接口,以满足用户需求,使类(对象)之间接口的不兼容性问题通过适配器方法得以解决 demo实例:1.适配参数对象,使传入的参数完整 2.适 ...

  9. 十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众

    一:文起缘由 写这一篇的目的源自于最近看同事在写wcf的时候,用特别感觉繁琐而且云里雾里的嵌套try catch来防止client抛出异常,特别感觉奇怪,就比如下面的代码. public void S ...

  10. CSS之旅——第二站 如何更深入的理解各种选择器

    上篇我们说了为什么要使用css,这篇我们就从选择器说起,大家都知道浏览器会把远端过来的html解析成dom模型,有了dom模型,html就变成 了xml格式,否则的话就是一堆“杂乱无章”的string ...