【noip 2015】运输计划

来源:互联网 发布:生化危机解析知乎 编辑:程序博客网 时间:2024/05/18 00:00

去题面的传送门
题目的意思是:求将一棵树上的任意一条边权赋值为0时,所有航线的最长长度的最小值
想到二分答案
如何验证?
既然我们二分的答案是最长路线,也就是说,在将一条边权赋值为0之后,所有的路线长度应该都小于等于mid。但是只能删掉一条边,所以这条边是所有删边之前长度小于mid的路线的交边。问题转化为,能否找到一条边,被所有长度大于mid的路线经过。所以我们要统计每一条边被经过的次数。统计的方法便是树上差分了。需要把边的信息映射到点上面去,也就是把边的权值映射为儿子节点的点权。在差分时,路线两端点各+1,两点的lca-2,从子节点一直求前缀和,到根节点,便求出了每条边的经过次数。此外,在求前缀和时,如果每次都dfs一遍,可能会超时,但是父节点和子节点的序号大小是没有关系的,不好直接遍历。为了方便,我们求每一个点的dfs序,由于子节点的dfs序一定大于父亲节点,所以按照dfs序的大小直接for一遍求前缀和。最后,把所有点权for一遍,如果存在某一个经过次数恰好为长度大于mid的路径的条数,切它的权值大于等于各点权与mid的最大差值(也就是说,如果去掉它,一定保证所有的路线长度不超过mid),这时mid是合法的,二分左区间,找更小的答案。

以上是我自己打的解析。
———————————————-LXT的萌萌分割线—————————————————–
来看一下dalao的题解(写的还是蛮好的):

满分做法:
官方正解:
1.tarjan离线求lca+二分+树上差分;
2.树链剖分+二分+树上差分+dfs序;
Ps:树链剖分求lca的代码量比倍增小,并且效率高, 并且不难,建议大家学一下;
说一下第二种解法:
明确:
1.我们要删的边一定在最长路线上;
2.我们将边的信息映射到点上,也就是说儿子与父亲的边的信息,我们放在了儿子身上;
首先,最大值最小化,我们可以想到二分这个最大路线长度;
假设我们已经二分出一个mid:删边后最大路线的长度;
1.所有大于mid的路线都必须删去一条边,使之小于或等于mid;
2.总共只能删一条边;
联立1,2得:
我们要删的边出现在所有大于mid的线路上,删去这条边,原本最长的路线要等于mid,其他路线小于mid;
此时我们的mid是合法答案;
如何求一条边被所有路线经过的次数?
树上差分!
对于条路线,我们将它起点的边+1,终点的边+1,lca上面的边-2;
最后求一遍树上前缀和,就可以得到答案;
但是,如果我们每次都从根节点dfs一遍求前缀和,它的复杂度虽然是O(n),但递归会耗费大量时间,会被卡掉最后一个点;所以我们考虑用dfs序来求解;
在处理树上信息时顺便求出dfs序;
然后我们从后往前求前缀和就可以了,这样是没有后效性的,因为我们dfs时先访问父亲,再访问儿子,父亲的dfs序必然小于它儿子的dfs序,如果从后往前,必然先更新儿子,再更新父亲,复杂度是常数很小的O(n);
总复杂度:O(nlogn);

PS:这道题调试了很久,最后才发现挂在了lca上。求fa数组时,for循环的顺序一定不要搞错!因为父亲节点的序号和子节点的序号没有大小关系!如果把枚举节点序号放在外层循环,序号小的不一定是序号大的的父节点!

代码:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;const int maxn=300000+10;int n,m,cnt,Index,l=-1,r,ans;int fist[maxn],nxt[maxn<<1],rank[maxn],deep[maxn],dfn[maxn];int fa[maxn][22],vpoint[maxn],cha[maxn];struct hh{    int f,t,v;}e[maxn<<1];struct lxt{    int f,t,len,lca;}ee[maxn];void build(int f,int t,int v){    e[++cnt]=(hh){f,t,v};    nxt[cnt]=fist[f];    fist[f]=cnt;}int make_lca(int x,int y){    if(deep[x]<deep[y]) swap(x,y);    for(int i=log2(n);i>=0;--i)      if(deep[fa[x][i]]>=deep[y])        x=fa[x][i];    if(x==y) return x;    for(int i=log2(n);i>=0;--i)      if(fa[x][i]!=fa[y][i])      {        x=fa[x][i];        y=fa[y][i];      }    return fa[x][0];}void dfs(int f,int t,int v){    fa[t][0]=f;    deep[t]=deep[f]+1;    rank[t]=v;    dfn[++Index]=t;    for(int i=fist[t];i!=-1;i=nxt[i])      if(e[i].t!=f)      {        vpoint[e[i].t]=e[i].v;        dfs(t,e[i].t,v+e[i].v);      }}void done(){    for(int i=1;i<=log2(n);++i)       for(int j=1;j<=n;++j)          fa[j][i]=fa[fa[j][i-1]][i-1];}bool check(int mid){    int maxx=0,tot=0;    memset(cha,0,sizeof(cha));    for(int i=1;i<=m;++i)      if(ee[i].len>mid)      {        tot++;        maxx=max(maxx,ee[i].len-mid);        cha[ee[i].f]++;        cha[ee[i].t]++;        cha[ee[i].lca]-=2;      }    if(!tot) return true;    for(int i=n;i>=1;--i) cha[fa[dfn[i]][0]]+=cha[dfn[i]];    for(int i=2;i<=n;++i)      if(cha[i]==tot&&vpoint[i]>=maxx) return true;    return false;}int main(){    memset(fist,-1,sizeof(fist));     scanf("%d%d",&n,&m);    for(int i=1;i<n;++i)    {        int a,b,t;        scanf("%d%d%d",&a,&b,&t);        r+=t;        build(a,b,t);        build(b,a,t);    }    dfs(0,1,0);    done();    for(int i=1;i<=m;++i)    {        int f,t,len,lca;        scanf("%d%d",&f,&t);        lca=make_lca(f,t);        len=rank[f]+rank[t]-2*rank[lca];        ee[i]=(lxt){f,t,len,lca};    }    while(r-l>1)    {        int mid=(l+r)>>1;        if(check(mid)) r=mid;        else l=mid;    }    if(check(l)) ans=l;    else ans=r;    printf("%d",ans);    return 0;}
原创粉丝点击