NOIP2015 运输计划

来源:互联网 发布:印度数学网络课程 编辑:程序博客网 时间:2024/06/11 15:23

Problem

既然是NOIP的题目,那么……
意料之中的偷懒不可避免

Solution

Actually,刚开始看这道题目的时候没有什么想法,然后就手贱点开了标签——树链剖分!!!
于是我果断就怂了,过了几天,我现在又才继续写,发现好像也不一定需要树链剖分,不过树链剖分比较好写吧可能。树链剖分我只是看了看,也没有实现了,好像就是将树分成几条链,这样就可以在链上用线段树快速维护出需要的信息。当然,我还是先不谈论这种高深的话题了。
如果等我学了树链剖分之后,我还记得这件事的话,我就填坑。


首先,我们发现可以枚举这个最短距离k,而且还可以二分答案。我经过卡评测之后发现maxr设到2108就可以A了。
其次为了好查询距离,我们可以记录每个节点到根节点的距离,再记录每组运输计划两节点的公共祖先,然后就可以利用sum[x]+sum[y]2sum[lca]快速地算出距离。找出距离大于k的计划,然后为了使它们的路径距离变小,应该都需要建立虫洞,那么最优当然就是建立在它们的公共交点上,枚举这些交点即可判断是否可行。
所以关键问题就在于如何快速求这个公共交点。对于一个距离超过k的运输计划x->y,记录路径对节点的覆盖次数,利用差分快速修改,cha[x]+=1;cha[y]+=1;cha[lca]=2。最后在重新dfs一遍就可以获得每个点的覆盖次数。由于虫洞只能改某条航道,那就记录maxn为最长可能减少的距离,再与最长的路径maxdis相比较,如果减去之后仍然大于枚举出的k,那么显然不成立,否则就可行。依次来二分答案。
貌似这道题还需要一点卡常技巧,我也不知道考NOIP的时候,能不能卡进去,但至少交到OJ上A了。

Code

#include <algorithm>#include <iostream>#include <cstring>#include <cstdio>using namespace std;const int size=300010,maxr=200000000;struct data{    int v,w,nxt;}edge[size<<1];int n,m,p,head[size],deep[size],pre[size][21];int s[size],t[size],lca[size],cha[size],sum[size];template <typename Tp> inline void read(Tp &x){    x=0;    char ch=getchar();    while(ch<'0'||ch>'9') ch=getchar();    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();}inline int max(int x,int y){return x>y?x:y;}inline void insert(int u,int v,int w){    edge[++p].v=v;edge[p].w=w;    edge[p].nxt=head[u];head[u]=p;    edge[++p].v=u;edge[p].w=w;    edge[p].nxt=head[v];head[v]=p;}void dfs(int x,int fa){    for(int i=1;i<=20;i++)      pre[x][i]=pre[pre[x][i-1]][i-1];    for(int i=head[x];i;i=edge[i].nxt)      if(edge[i].v!=fa)      {        deep[edge[i].v]=deep[x]+1;        pre[edge[i].v][0]=x;        sum[edge[i].v]=sum[x]+edge[i].w;        dfs(edge[i].v,x);      }}int getlca(int x,int y){    if(deep[x]>deep[y])      swap(x,y);    int t=deep[y]-deep[x];    for(int i=20;i>=0;i--)      if(t&(1<<i))        y=pre[y][i];    if(x==y)      return x;    for(int i=20;i>=0;i--)      if(pre[x][i]!=pre[y][i])        x=pre[x][i],y=pre[y][i];    return pre[x][0];}void input(){    int tu,tv,tw;    read(n),read(m);    for(int i=1;i<n;i++)    {        read(tu),read(tv),read(tw);        insert(tu,tv,tw);    }    dfs(1,0);    for(int i=1;i<=m;i++)    {        read(s[i]),read(t[i]);        lca[i]=getlca(s[i],t[i]);    }}void update(int x,int fa){    for(int i=head[x];i;i=edge[i].nxt)      if(edge[i].v!=fa)      {        update(edge[i].v,x);        cha[x]+=cha[edge[i].v];      }}bool check(int k){    memset(cha,0,sizeof(cha));    int num=0,maxn=0,maxdis=0;    for(int i=1;i<=m;i++)      if(sum[s[i]]+sum[t[i]]-2*sum[lca[i]]>k)      {        num++;        maxdis=max(maxdis,sum[s[i]]+sum[t[i]]-2*sum[lca[i]]);        cha[s[i]]++,cha[t[i]]++,cha[lca[i]]-=2;      }    update(1,0);    for(int i=2;i<=n;i++)      if(cha[i]>=num)        maxn=max(maxn,sum[i]-sum[pre[i][0]]);    if(maxdis-maxn>k)      return false;    return true;}int main(){    int l=0,r=maxr,m;    input();    while(l<r)    {        m=(l+r)>>1;        if(check(m))          r=m;        else          l=m+1;    }    printf("%d\n",l);    return 0;}