[NOIP2015]运输计划 树链剖分 二分 差分

运输计划

  • 关键字:树链剖分,二分,差分

结果和评价

  • 得分:70
  • 时间
  • 评价:不应该

我的思路

  • 暴力直接60
  • 直接否定了二分,然后瞎搞,多水了10,没有什么思考。。。

正确思路

  • 求最长路径最短,应该要先考虑考虑二分。
  • 二分一个答案之后,比答案小的 rout 都无需考虑了
    • 然后我们考虑一下要删什么边,显然要删边一定在到剩下 rout 的交集里。
    • 而且一定要满足可以从中取出一条边满足 MxroutMxedge<=limt

讲道理这个思路是非常顺的。

怎么说呢,可以思考一下只有一条边的情况,我们要删的边一定是路径上最长的边。

也就是说,我们要删的边是使最后路径全部合法的边。是可以确定的。

  • 然后我们发现现在有个问题需要解决
    • 高效得覆盖路径
    • 高效查询出所以路径交集中的最大边
  • 怎么覆盖呢。。。
  • 先看看链的情况。对于一个路径 s,t ,我们可以用一个 cov 数组记录每个点被覆盖了几次。
  • 然后把 cov[s+1,t] (我们用标记子节点的方式标记边)都加1。。。复杂度为 O(nm) 的,没有什么屁用。
  • 发现了吧,完全可以用差分数组来实现这一覆盖的过程,复杂度为 O(n+m)
  • 这样就轻松地拿到了80分
  • 然后考虑一下树的情况。。。
  • 额,树上的路径覆盖。。。链上的路径覆盖。。。好像树剖一下就没有什么区别了。。。
  • 每次,直接树剖一下,就可以了。
  • 复杂度为 (log(nt)(log(n)m+n)
  • 由于树剖的常数是比较优的,所以可以轻松跑过。。

实现

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<ctype.h>
#include<time.h>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<string>

using namespace std;

#define bug(x) cerr<<#x<<'='<<x<<' '
#define debug(x) cerr<<#x<<'='<<x<<'\n'
#define For(i,a,b) for(register int i=a;i<=b;++i)
#define Ror(i,a,b) for(register int i=b;i>=a;--i)

typedef long long ll;

template<class T>void rd(T&x){
    x=0;char c,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;
    do x=(x<<1)+(x<<3)+(c^'0');
    while(c=getchar(),isdigit(c));
    x*=f;
}
template<class T>void pf(T x){
    static int top=0,stk[1000];
    if(!x)putchar('0');
    if(x<0)putchar('-'),x=-x;
    while(x)stk[++top]=x%10,x/=10;
    while(top)putchar(stk[top--]+'0');
}
template<class T>void pt(T x){pf(x),putchar(' ');}
template<class T>void ptn(T x){pf(x),putchar('\n');}
template<class T>void Max(T&x,T y){if(x<y)x=y;}
template<class T>void Min(T&x,T y){if(y<x)x=y;}

const int M=3e5+5,INF=1e9;

int fa[M],tp[M],son[M],sz[M],dep[M],n,m,dis[M],to[M];

struct node{int s,t,v;}rout[M];

struct E{int t,id,nxt;}G[M<<1];

int h[M],E_tot,cost[M],mp[M],Dfn,dfn[M];

void E_Add(int x,int y,int id){
    G[++E_tot]=(E){x,id,h[y]};h[y]=E_tot;
    G[++E_tot]=(E){y,id,h[x]};h[x]=E_tot;
}

void dfs(int x,int f,int d){
    dis[x]=d;
    fa[x]=f;sz[x]=1;
    dep[x]=dep[f]+1;
    for(int i=h[x];i;i=G[i].nxt){
        int y=G[i].t,c=cost[G[i].id];
        if(y==f)continue;
        to[y]=G[i].id;
        dfs(y,x,d+c);
        sz[x]+=sz[y];
        if(sz[son[x]]<sz[y])son[x]=y;
    }
}
void rfs(int x){
    mp[dfn[x]=++Dfn]=x;
    if(son[x]){
        tp[son[x]]=tp[x];
        rfs(son[x]);
    }
    for(int i=h[x];i;i=G[i].nxt){
        int y=G[i].t;
        if(y==fa[x]||y==son[x])continue;
        rfs(tp[y]=y);
    }
}

void init(){
    dfs(1,0,0);
    rfs(tp[1]=1);
}

int LCA(int x,int y){
    while(tp[x]!=tp[y]){
        if(dep[tp[x]]<dep[tp[y]])swap(x,y);
        x=fa[tp[x]];
    }
    if(dep[y]<dep[x])return y;
    return x;
}

int calc(node x){
    int s=x.s,t=x.t,v=LCA(s,t);
    return dis[s]+dis[t]-2*dis[v];
}

int Add[M];

void insert(int l,int r){
    ++Add[l],--Add[r+1];
}

void EAdd(int x,int y){
    while(tp[x]!=tp[y]){
        if(dep[tp[x]]<dep[tp[y]])swap(x,y);
        int l=dfn[tp[x]],r=dfn[x];
        insert(l,r);
        x=fa[tp[x]];
    }
    if(x!=y){
        int l=dfn[x],r=dfn[y];
        if(l>r)swap(l,r);
        insert(l+1,r);
    }
}
bool check(int limt){
    int cnt=0,Mxr=0,Mxe=0;
    memset(Add,0,sizeof(Add));
    For(i,1,m)if(limt<rout[i].v){
        ++cnt;
        EAdd(rout[i].s,rout[i].t);
        if(Mxr<rout[i].v)Mxr=rout[i].v;
    }
    int cov=0;
    For(i,1,n){
        cov+=Add[i];
        if(cov==cnt){
            int d=cost[to[mp[i]]];
            if(Mxe<d)Mxe=d;
        }
    }
    if(Mxr-Mxe<=limt)return 1;
    return 0;
}

int main(){

    rd(n),rd(m);
    For(i,1,n-1){
        int a,b;
        rd(a),rd(b),rd(cost[i]);
        E_Add(a,b,i);
    }
    init();
    For(i,1,m){
        rd(rout[i].s);
        rd(rout[i].t);
        rout[i].v=calc(rout[i]);
    }

    int l=0,r=3e8,res=r;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))res=mid,r=mid-1;
        else l=mid+1;
    }
    ptn(res);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值