NOIP2015 运输计划

6 篇文章 0 订阅
3 篇文章 0 订阅

Problem

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

Solution

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


首先,我们发现可以枚举这个最短距离k,而且还可以二分答案。我经过卡评测之后发现maxr设到 2108 2 ∗ 10 8 就可以A了。
其次为了好查询距离,我们可以记录每个节点到根节点的距离,再记录每组运输计划两节点的公共祖先,然后就可以利用 sum[x]+sum[y]2sum[lca] s u m [ x ] + s u m [ y ] − 2 ∗ s u m [ l c a ] 快速地算出距离。找出距离大于k的计划,然后为了使它们的路径距离变小,应该都需要建立虫洞,那么最优当然就是建立在它们的公共交点上,枚举这些交点即可判断是否可行。
所以关键问题就在于如何快速求这个公共交点。对于一个距离超过k的运输计划x->y,记录路径对节点的覆盖次数,利用差分快速修改, cha[x]+=1;cha[y]+=1;cha[lca]=2 c h a [ x ] + = 1 ; c h a [ y ] + = 1 ; c h a [ l c a ] − = 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值