Vijos[1983]NOIP2015Day2T3 运输计划 transport LCA

题目链接Vijos

题目链接UOJ

转载一个大佬的题解:

点击这里->银牌爷题解

主要考察二分查找、树上倍增、贪心、“树上前缀和”。
题目是一颗树,要求将一条边的权值变为0,使得所有运输计划的最大时间最小。
直觉告诉我们,这是一个树上倍增的题目,但是它却不像前几年的 Day2T3 开车旅行那样纯倍增,或许更像疫情控制一些,倍增只是辅助算法,还需要配合其他算法。
由于要使所有运输计划的最大时间最小,不难想到二分答案的方法。
使C(t)表示是否可以改造一条边,使得改造之后所有运输计划中最长的时间不大于t。这是惯用伎俩,用二分的的话,我们就可以确定一个变量t,正因为有了这个t,我们才能有的放矢的进行贪心或是干别的。
如何判断C(t)呢?在开始的时候用倍增预处理出所有计划的时间,如果小于等于t,就可以忽略,如果大于t,那么就要考虑在其路径上改造一条边。
由于所有时间大于t的计划都要改造一条边,问题就变为了求所有时间大于t的计划的路径交集,改造其中一条最大的边,看看去掉这条边之后,是否可以满足条件。
问题来了,如何求交集呢?
这里给出两种方法,一种是模拟求交集。一种是利用树上前缀和求交集。

如果设total为超出时间t的方案数量,边e?i??经过的次数cnt?i??。对于每个超出时间t的方案,将其路径中边的cnt加1。最后,所有cnti=totalcnt?i??的边就是我们要求的的交集。
然而每次给每条边加一肯定是不现实的,所以我们要想出一个高效的方法,来维护边被经过的次数。
这个方法像极了2012年NOIP的借教室,不过是树上的版本。我们先看看借教室的怎么处理区间加减的。
如果要对一段连续区间[a,b)同时加上一个值,只需在开始处加上这个值,在结束后减去这个值,维护前缀和就行了。看上去应该是这样的:

若初始都是0,让连续区间[a,b)同时加上一个值m之后,前缀和 即为元素i的值。下面是前缀和:

如果有多组加减,也不会冲突。
同样的,在树上,我们用s?i??来表示顶点i到其父亲的这条边被经过的次数,v?i??用于记录顶点信息。
对于每个点对(a,b),我们将v?a??+1,v?b??+1,v?LCA(a,b)??-2。

则树上前缀和

利用dfs序,对于每个点更新它的父亲的s值,前缀和可以在O(n)的时间内算出来。这样,每条边经过的次数就顺利计算出来了。
这个方法的复杂度是线性的,为O(m+n)。

当然写代码也是写的很艰辛:

我力劝C++的同胞们,这题卡常数,Dfs党会吃亏,比如这里这个UOJ的数据

我们可以使用Bfs和尽量避免写Dfs,不然会Tle的

以下代码实测极端数据约900ms,正所谓卡常数,如果把一开始的dfs改为bfs可能会更快……(博主很懒,不改了)

总结一下大佬的题解:

1. Dfs或Bfs构建树,然后记录下各种信息,现在主要是以下几点:

  1)子节点      son[rt]    vector <int>

  2)深度       deep[rt]    int

  3)到根节点的距离  dis[rt]    int

  4)到父亲节点的距离 fadis[rt]   int

  5)父亲       father[rt]   int

2. LCA的预处理,处理出F[rt][i],表示节点rt的第2i个祖先(即节点rt的祖先中与之深度相差rt的祖先)    // F[rt][i]  在代码中写成 Anst[rt][i]

转移表达式为:F[rt][i]=F[F[rt][i-1]][i-1] 应该都能够理解

3. 求取LCA: 这里用的倍增的方法,虽然比离线算法LCA_Tarjan慢一个log,但是倍增是一个好东西,不妨去练练。这样思考:对于两个深度为d的节点a和b,使得int i=log2(d),那么就可以倍增:对于节点a和b,如果他们的第2i个祖先是相同的,那么他们在网上的祖先也一定是相同的 ,那么我们就对于不改变a和b的值,而使i=i-1;如果他们的第2i个祖先不同,那么他们往下走的祖先也是不同的,于是就可以确定他们的最近公共祖先一定是在第2i个祖先上面的,那么我们就可以安心的把a和b的值更新乘F[a][i]和F[b][i](a=F[a][i],b=F[b][i])。直到i为0位置,无法再做了。于是,a和b的最近公共祖先就是a和b的父亲,即F[a][0]或F[b][0](相等的)。

至于LCA_Tarjan,可以自己学啊!这里就不多说了。

3.二分答案:不用说了吧,就是一个基本的二分

4.check(答案):这个在大佬的题解里面写的比较详细,可以看他的~

  1 #pragma comment(linker, "/STACK:10240000,10240000")
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <cstdio>
  5 #include <cstdlib>
  6 #include <cmath>
  7 #include <vector>
  8 using namespace std;
  9 const int N=300000+5,M=N*2,Inf=N*1000;
 10 void read(int &x){
 11     x=0;
 12     char ch=getchar();
 13     while (!(‘0‘<=ch&&ch<=‘9‘))
 14         ch=getchar();
 15     while (‘0‘<=ch&&ch<=‘9‘){
 16         x=x*10+ch-48;
 17         ch=getchar();
 18     }
 19 }
 20 struct Edge{
 21     int cnt,y[M],z[M],nxt[M],fst[N];
 22     void set(){
 23         cnt=0;
 24         memset(y,0,sizeof y);
 25         memset(z,0,sizeof z);
 26         memset(nxt,0,sizeof nxt);
 27         memset(fst,0,sizeof fst);
 28     }
 29     void add(int a,int b,int c){
 30         cnt++;
 31         y[cnt]=b,z[cnt]=c;
 32         nxt[cnt]=fst[a],fst[a]=cnt;
 33     }
 34 }e;
 35 int n,m;
 36 vector <int> Tree[N];
 37 int father[N],son[N],deep[N],dis[N],fadis[N],bh[N],bhtot;
 38 int Anst[N][20];//Ancestor
 39 struct Query{
 40     int x,y,LCA,cost;
 41 }q[N];
 42 int Nextsum[N];
 43 void Build_Tree(int prev,int rt){
 44     bh[++bhtot]=rt;
 45     Tree[rt].clear();
 46     deep[rt]=deep[prev]+1;
 47     son[rt]=0;
 48     father[rt]=prev;
 49     for (int i=e.fst[rt];i;i=e.nxt[i])
 50         if (e.y[i]!=prev){
 51             son[rt]++,Tree[rt].push_back(e.y[i]);
 52             fadis[e.y[i]]=e.z[i];
 53             dis[e.y[i]]=dis[rt]+e.z[i];
 54             Build_Tree(rt,e.y[i]);
 55         }
 56 }
 57 void LCA_Prepare(){
 58     memset(Anst,0,sizeof Anst);
 59     for (int i=1;i<=n;i++){
 60         int rt=bh[i];
 61         Anst[rt][0]=father[rt];
 62         for (int i=1;(1<<i)<=deep[rt];i++)
 63             Anst[rt][i]=Anst[Anst[rt][i-1]][i-1];
 64     }
 65 }
 66 int LCA(int a,int b){
 67     if (deep[a]>deep[b])
 68         swap(a,b);
 69     for (int i=deep[b]-deep[a],j=0;i>0;i>>=1,j++)
 70         if (i&1)
 71             b=Anst[b][j];
 72     if (a==b)
 73         return a;
 74     int k;
 75     for (k=0;(1<<k)<=deep[a];k++);
 76     for (;k>=0;k--)
 77         if ((1<<k)<=deep[a]&&Anst[a][k]!=Anst[b][k])
 78             a=Anst[a][k],b=Anst[b][k];
 79     return Anst[a][0];
 80 }
 81 bool check(int t){
 82     int total=0,Maxcost=0,Maxcut=0;
 83     memset(Nextsum,0,sizeof Nextsum);
 84     for (int i=1;i<=m;i++)
 85         if (q[i].cost>t){
 86             Maxcost=max(Maxcost,q[i].cost-t);
 87             total++;
 88             Nextsum[q[i].x]++;
 89             Nextsum[q[i].y]++;
 90             Nextsum[q[i].LCA]-=2;
 91         }
 92     for (int i=n;i>=1;i--)
 93         Nextsum[father[bh[i]]]+=Nextsum[bh[i]];
 94     for (int i=1;i<=n;i++)
 95         if (Nextsum[i]==total)
 96             Maxcut=max(Maxcut,fadis[i]);
 97     return Maxcost<=Maxcut;
 98 }
 99 int main(){
100     scanf("%d%d",&n,&m);
101     e.set();
102     for (int i=1;i<n;i++){
103         int a,b,c;
104         read(a),read(b),read(c);
105         e.add(a,b,c);
106         e.add(b,a,c);
107     }
108     bhtot=0;
109     deep[0]=-1,dis[1]=fadis[1]=0;
110     Build_Tree(0,1);
111     LCA_Prepare();
112     for (int i=1;i<=m;i++){
113         read(q[i].x),read(q[i].y);
114         q[i].LCA=LCA(q[i].x,q[i].y);
115         q[i].cost=dis[q[i].x]+dis[q[i].y]-dis[q[i].LCA]*2;
116     }
117     int le=0,ri=Inf,mid,ans=0;
118     while (le<=ri){
119         mid=(le+ri)>>1;
120         if (check(mid))
121             ri=mid-1,ans=mid;
122         else
123             le=mid+1;
124     }
125     printf("%d",ans);
126     return 0;
127 }

代码

时间: 2024-12-18 05:26:26

Vijos[1983]NOIP2015Day2T3 运输计划 transport LCA的相关文章

P2680 运输计划[二分+LCA+树上差分]

题目描述 公元20442044 年,人类进入了宇宙纪元. L 国有 nn 个星球,还有 n-1n?1 条双向航道,每条航道建立在两个星球之间,这 n-1n?1 条航道连通了 LL 国的所有星球. 小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u_i*u**i* 号星球沿最快的宇航路径飞行到 v_i*v**i* 号星球去.显然,飞船驶过一条航道是需要时间的,对于航道 jj,任意飞船驶过它所花费的时间为 t_j*t**j*,并且任意两艘飞船之间不会产生任

noip 2015 运输计划 (lca+二分)

/* 95 最后一个点T了 qian lv ji qiong 了 没学过树剖 听chx听xzc说的神奇的方法 Orz 首先求出每个计划的路径长度 这里写的倍增 然后二分答案 对于每个ans 统计>他的路径条数 tot 并维护最大差值 dec 并且对于每条不合法的路径维护每个点的经过次数 然后枚举点 如果经过次数==tot说明每一条不合法的都经过他 然后尝试把它建成虫洞 如果他对应边的权值>=dec 那么我们删掉它ans就合法了 关键是统计每个点在非法路径中的经过次数 : 维护sum数组 对于每

UOJ150 运输计划

运输计划(transport.cpp/c/pas)[问题描述]公元 2044 年,人类进入了宇宙纪元.L 国有 n 个星球,还有 n-1 条 双向 航道,每条航道建立在两个星球之间,这 n-1 条航道 连通 了 L 国的所有星球.小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u i 号星球沿 最快 的宇航路径飞行到 v i 号星球去.显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 t j ,并且任意两艘飞船之间 不会 产

BZOJ 4326:NOIP2015 运输计划(二分+差分+lca)

NOIP2015 运输计划Description公元 2044 年,人类进入了宇宙纪元.L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球.小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰.为了鼓励科技创新, L

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

4326: NOIP2015 运输计划 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 308  Solved: 208[Submit][Status][Discuss] Description 公元 2044 年,人类进入了宇宙纪元.L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球.小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui

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

[NOIP2015]运输计划 D2 T3 Description 公元2044年,人类进入了宇宙纪元. L国有n个星球,还有n-1条双向航道,每条航道建立在两个星球之间,这n-1条航道连通了L国的所有星球. 小P掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从ui号星球沿最快的宇航路径飞行到vi号星球去.显然,飞船驶过一条航道是需要时间的,对于航道j,任意飞船驶过它所花费的时间为tj,并且任意两艘飞船之间不会产生任何干扰. 为了鼓励科技创新,L国国王同意小P的物流

[noip 2015]运输计划 [LCA][树链剖分]

用了luogu上的题目描述 题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创

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

我太懒了 吃掉了题面 题解 & 吐槽 一道很好的树上差分练习题. 不加fread勉强a过bzoj和luogu的数据,加了fread才能在uoj里卡过去. 可以发现,答案则是运输计划里花费的最大值,最大值最小,便是二分答案的标志. 那么该怎么check呢... 我们得找出所有超过限制的计划,这个过程可以在LCA倍增的过程中预处理出来. 然后再找出一些被这些计划都覆盖的边,找到最大的那条边,如果最大的计划花费减去最大的那条边小于x,那么x就是可行的. 但是该怎么找到那些被计划都覆盖的边呢... 我们

[NOIP2015] 提高组 洛谷P2680 运输计划

题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创新,L 国国王同意小 P 的