题目描述 Description |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
公元 2044 年,人类进入了宇宙纪元。L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
输入描述 Input Description |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。数据保证 1≤ai,bi≤n 且 0≤ti≤1000。接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1≤ui,vi≤n | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
输出描述 Output Description |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
输出文件只包含一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
样例输入 Sample Input |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6 3 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
样例输出 Sample Output |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
11 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
数据范围及提示 Data Size & Hint |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
之前的一些废话:还有一天出国,某年NOIP,由于我对NOIP的题都有心理阴影,所以一直不敢把这些没做的最后一题的坑填上,然而今天还是面对了这道题,并且能很快想出做法还是很开心的。
题解:首先蜜汁想到了二分(我也不知道为什么),二分的套路,判断当前解mid是否可行,如何判断是否可行呢?其实就是查看当前这些航线能否通过删去某一条对应的边使得每条路径和都小于mid即可。对于那些原来路径和就比mid小的航线就可以无视它们,只考虑那些需要进行裁剪的航线,我们现在需要找到一条边,使得所有比mid大的航线都必须经过这条边(要不然那些不过这条边的航线就无法进行剪裁,更不必说当前解可行了)。满足这种条件的边可能有很多,我们根据贪心的思路就选取最大的边,然后用路径和最大的那条航线减去这条边权(如果最大的那条航线都可以的话比他小的肯定都可以),并且判断这个结果与mid的大小关系,如果小于等于mid,那就说明当前解可行。
当然这只是一些想法,具体做法如下:首先我们通过dfs预处理出1到所有点的距离,这样方便后续航线路径和的计算,并把每一条航线进行结构体捆绑,按照路径和进行从小到大的排序。之后开始二分,上界是最大航线的路径和,下界是0,判断当前解是否可行的话用upper_bound在有序的航线中查出比mid大的第一条航线,然后开始考虑这些路径,需要找到一条边过所有航线,这个可以拿树上差分在O(n)时间内完成,最后扫一遍所有的边,判断tag[i]是否等于总航线的数值,并取其中的最大值,然后用路径和最大的那条航线减去这条边权,并且判断这个结果与mid的大小关系,如果小于等于mid,那就说明当前解可行。在树上差分的时候需要求出lca,因为我不会写O(1)求lca的,所以会多一个log,但是我们在预处理所有航线的路径和的过程中就会算出lca的值,所以提前存好即可。复杂度O((n+logm)logn)
二分照样很坑爹,改过N次二分的写法还是觉得不够稳,最后决定放弃,用最原始的写法,毕竟这样保险:
代码:
#include<iostream> #include<cmath> #include<cstring> #include<algorithm> #include<cstdio> #include<queue> using namespace std; typedef long long LL; typedef pair<int,int> PII; #define mem(a,b) memset(a,b,sizeof(a)) inline int read() { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int maxn=300010,maxm=300010; int n,m,a,b,c,ce=-1,es,top[maxn],first[maxn],deep[maxn],fa[maxn],size[maxn],dis[maxn],ms[maxn],L,R,mid; int tag[maxn],maxw,w[maxn]; struct Edge { int u,v,w,next; Edge() {} Edge(int _1,int _2,int _3,int _4) :u(_1),v(_2),w(_3),next(_4) {} }e[maxn<<1]; struct path { int u,v,lc,len; path() {} path(int _1,int _2,int _3,int _4):u(_1),v(_2),lc(_3),len(_4) {} bool operator < (const path& s)const {return len<s.len;} }line[maxm]; void addEdge(int a,int b,int c) { e[++ce]=Edge(a,b,c,first[a]);first[a]=ce; e[++ce]=Edge(b,a,c,first[b]);first[b]=ce; } void dfs(int now,int pa) { size[now]=1; for(int i=first[now];i!=-1;i=e[i].next) if(e[i].v!=pa) { deep[e[i].v]=deep[now]+1; dis[e[i].v]=dis[now]+e[i].w; w[e[i].v]=e[i].w; dfs(e[i].v,now); if(size[ms[now]]<size[e[i].v])ms[now]=e[i].v; size[now]+=size[e[i].v]; fa[e[i].v]=now; } } void divide(int now,int chain) { top[now]=chain; if(ms[now])divide(ms[now],chain); for(int i=first[now];i!=-1;i=e[i].next)if(e[i].v!=fa[now] && e[i].v!=ms[now])divide(e[i].v,e[i].v); } int lca(int a,int b) { while(top[a]!=top[b]) { if(deep[top[a]]<deep[top[b]])swap(a,b); a=fa[top[a]]; } return deep[a]<deep[b] ? a : b; } void pushup(int now) { for(int i=first[now];i!=-1;i=e[i].next) if(fa[now]!=e[i].v)pushup(e[i].v),tag[now]+=tag[e[i].v]; } bool check(int mid) { int maxw=0; path tmp=path(0,0,0,mid); int pos=upper_bound(line,line+m,tmp)-line; mem(tag,0); for(int i=pos;i<m;i++) { tag[line[i].u]++;tag[line[i].v]++; tag[line[i].lc]-=2; } pushup(1); for(int i=2;i<=n;i++)if(tag[i]==m-pos)maxw=max(maxw,w[i]); return line[m-1].len-maxw<=mid; } int main() { mem(first,-1); n=read();m=read(); for(int i=1;i<n;i++)a=read(),b=read(),c=read(),addEdge(a,b,c); dfs(1,0);divide(1,1); for(int i=0;i<m;i++) { a=read();b=read();c=lca(a,b); line[i]=path(a,b,c,dis[a]+dis[b]-2*dis[c]); } sort(line,line+m); L=0;R=line[m-1].len; while(R-L>1) { mid=(L+R)>>1; if(check(mid))R=mid; else L=mid; } if(check(L))printf("%d ",L); else printf("%d ",R); return 0; }
总结:树上差分真的太好使了!!二分还是老老实实按上面那么写吧。