UOJ#150 NOIP2015 运输计划

Posted lcf2000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UOJ#150 NOIP2015 运输计划相关的知识,希望对你有一定的参考价值。

题目描述

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

L 国有 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n1 条航道连通了 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。数据保证 1ai,bin 且 0ti1000

接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和vj,表示第 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1ui,vin1≤ui,vi≤n

输出格式

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

 

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

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

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

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

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

  下面是代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #define INF 2147483647
 5 #define maxn 300001
 6 
 7 using namespace std;
 8 
 9 struct data{
10     int f,t,v;
11 }he[maxn];
12 int fa[maxn],head[maxn*2],to[maxn*2],next[maxn*2],c[maxn*2];
13 int dep[maxn],son[maxn],top[maxn],siz[maxn],w[maxn],zui;
14 int tc[maxn],fc1[maxn],fc[maxn],cha[maxn+1],tt,m,n,x,y,l,r;
15 
16 int getint(){
17     int w=0,q=0;
18     char c=getchar();
19     while((c>9||c<0)&&c!=-) c=getchar();
20     if(c==-) q=1,c=getchar();
21     while(c>=0&&c<=9) w=w*10+c-0,c=getchar();
22     return q?-w:w; 
23 }
24 
25 void dfs1(int u,int dd){
26     dep[u]=dd;siz[u]++;
27     for(int i=head[u];i;i=next[i])
28         if(!dep[to[i]]){
29             dfs1(to[i],dd+1);siz[u]+=siz[to[i]];
30             fa[to[i]]=u;fc[to[i]]=c[i];
31             if(siz[to[i]]>=siz[son[u]]) son[u]=to[i];
32         }
33 }
34 
35 void dfs2(int u,int dd){
36     w[u]=++tt;top[u]=dd;
37     fc1[tt]=fc[u];
38     if(son[u]) tc[son[u]]=tc[u]+fc[son[u]],dfs2(son[u],dd);
39     for(int i=head[u];i;i=next[i])
40         if(to[i]!=son[u]&&to[i]!=fa[u])
41             dfs2(to[i],to[i]);
42 }
43 
44 inline int lca(int x,int y){
45     int ju=0;
46     while(top[x]!=top[y]){
47         int a=fa[top[x]],b=fa[top[y]];
48         if(dep[a]<dep[b]) swap(x,y),swap(a,b);
49         cha[w[x]+1]--;cha[w[top[x]]]++;
50         ju+=tc[x]+fc[top[x]];x=a;
51     }
52     if(dep[x]<dep[y]) swap(x,y);
53     cha[w[x]+1]--;cha[w[son[y]]]++;
54     ju+=tc[x]-tc[y];
55     return ju;
56 }
57 
58 inline bool pd(int kk){
59     int ci=0;
60     memset(cha,0,sizeof(cha));
61     for(register int i=1;i<=m;i++)
62         if(he[i].v>kk) lca(he[i].f,he[i].t),ci++;
63     int hehe=0,hh=c[0];
64     for(register int i=1;i<=tt;i++){
65         hh+=cha[i];
66         if(hh==ci) hehe=max(hehe,fc1[i]);
67     }
68     if(zui-hehe<=kk) return 1;
69     return 0;
70 }
71 
72 int main(){
73     n=getint();m=getint();
74     for(int i=1;i<n;i++){
75         x=getint();y=getint();
76         to[++tt]=y,next[tt]=head[x],head[x]=tt;
77         to[++tt]=x,next[tt]=head[y],head[y]=tt;
78         c[tt-1]=c[tt]=getint();
79     }
80     dfs1(1,1);tt=0;dfs2(1,1);
81     for(int i=1;i<=m;i++){
82         he[i].f=getint();he[i].t=getint();
83         he[i].v=lca(he[i].f,he[i].t);
84         r=max(r,he[i].v);
85     }
86     zui=r++;
87     while(l!=r){
88         int mid=(l+r)>>1;
89         if(pd(mid))r=mid;
90         else l=mid+1;
91     }
92     printf("%d",l);
93     return 0;
94 }

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

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

  下面贴代码:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<cmath>
  6 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
  7 #define maxn 300010
  8 
  9 using namespace std;
 10 typedef long long llg;
 11 
 12 struct data{
 13     int u,v,g,x;
 14 }s[maxn];
 15 int n,m,dep[maxn],son[maxn],siz[maxn],fa[maxn],tc[maxn],top[maxn],fc[maxn];
 16 int head[maxn],to[maxn<<1],next[maxn<<1],c[maxn<<1],tt,K,nn,now;
 17 int dc[maxn],cd[maxn],ld,b[maxn],lb,bb[maxn],ca[maxn];
 18 
 19 int getint(){
 20     int w=0;bool q=0;
 21     char c=getchar();
 22     while((c>9||c<0)&&c!=-) c=getchar();
 23     if(c==-) c=getchar(),q=1;
 24     while(c>=0&&c<=9) w=w*10+c-0,c=getchar();
 25     return q?-w:w;
 26 }
 27 
 28 void link(int x,int y){
 29     to[++tt]=y;next[tt]=head[x];head[x]=tt;
 30     to[++tt]=x,next[tt]=head[y];head[y]=tt;
 31     c[tt]=c[tt-1]=getint();
 32 }
 33 
 34 void dfs(int u){
 35     siz[u]=1;
 36     for(int i=head[u],v;v=to[i],i;i=next[i])
 37         if(!siz[v]){
 38             dep[v]=dep[u]+1; fa[v]=u; fc[v]=c[i];
 39             dfs(v); siz[u]+=siz[v];
 40             if(siz[v]>siz[son[u]]) son[u]=v;
 41         }
 42 }
 43 
 44 void dfs(int u,int d){
 45     top[u]=d;
 46     if(son[u]) tc[son[u]]=tc[u]+fc[son[u]],dfs(son[u],d);
 47     for(int i=head[u],v;v=to[i],i;i=next[i])
 48         if(!top[v]) dfs(v,v);
 49 }
 50 
 51 inline int lca(int u,int v){//树链剖分求lca
 52     now=0;
 53     while(top[u]!=top[v]){
 54         if(dep[fa[top[u]]]<dep[fa[top[v]]]) swap(u,v);
 55         now+=tc[u]+fc[top[u]]; u=fa[top[u]];
 56     }
 57     if(dep[u]>dep[v]) swap(u,v);
 58     now+=tc[v]-tc[u]; return u;
 59 }
 60 
 61 void kou(int u,int v,int g){//把最长路径抠出来
 62     while(u!=g) cd[++ld]=u,ca[ld]=fc[u],u=fa[u]; cd[++ld]=g; //从u到g的路径
 63     while(v!=g) b[++lb]=v,bb[lb]=fc[v],v=fa[v]; //从v到g的路径
 64     while(lb) ca[ld]=fc[b[lb]],cd[++ld]=b[lb--]; //ca表示长度
 65     for(int i=1;i<=ld;i++) dc[cd[i]]=i;
 66 }
 67 
 68 inline bool pd(int x){//判断是否可行
 69     int l=1,r=ld,gi=0;
 70     for(register int i=1;i<=m;i++)
 71         if(s[i].x>x){
 72             l=max(l,s[i].u);
 73             r=min(r,s[i].v);
 74         }
 75     for(int i=l;i<r;i++) gi=max(gi,ca[i]);
 76     return nn-gi<=x;
 77 }
 78 
 79 inline void gi(int &xx,int u){
 80     int x,y;
 81     x=lca(u,s[K].u); if(dep[x]>dep[s[K].g]){xx=dc[x];return;}
 82     y=lca(u,s[K].v); if(dep[y]>dep[s[K].g]){xx=dc[y];return;}
 83     xx=dc[s[K].g];//点在路径之外,要么另一个点的两个lca在最长路径上,此时视为最长路径的最上点,要么两条路径并为空
 84 }
 85 
 86 int main(){
 87     n=getint(); m=getint();
 88     for(int i=1;i<n;i++) link(getint(),getint());
 89     dep[1]=1;dfs(1); dfs(1,1);
 90     for(register int i=1;i<=m;i++){
 91         s[i].u=getint(); s[i].v=getint();
 92         s[i].g=lca(s[i].u,s[i].v); s[i].x=now;
 93         if(now>nn) nn=now,K=i;//nn为最长路径长度,K为最长路径标号
 94     }
 95     kou(s[K].u,s[K].v,s[K].g);
 96     for(register int i=1,l,r;i<=m;i++)
 97         if(i!=K){
 98             gi(l,s[i].u); gi(r,s[i].v);//lca分类讨论求路径并
 99             if(l>r) swap(l,r);
100             s[i].u=l,s[i].v=r;//注意这里的区间为[l,r)
101         }
102     s[K].u=1,s[K].v=ld;
103     int l=0,r=nn,mid;
104     while(l!=r){
105         mid=l+r>>1;
106         if(pd(mid)) r=mid;
107         else l=mid+1;
108     }
109     printf("%d",l);
110 }

以上是关于UOJ#150 NOIP2015 运输计划的主要内容,如果未能解决你的问题,请参考以下文章

[NOIP2015提高组]运输计划

UOJ150 运输计划

NOIp2015 运输计划 [LCA] [树上差分] [二分答案]

Vijos[1983]NOIP2015Day2T3 运输计划 transport LCA

树链剖分UOJ150-Transportation Plan

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