luogu #2680 运输计划(二分答案+树上差分)(noip2015)

题目链接
标签:二分答案,树上差分

题目描述
L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物
流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰。
为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后, 这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的 物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞,试求出小 P 的物流公司完成阶段 性工作所需要的最短时间是多少?

输入输出格式
输入格式:
输入文件名为 transport.in。
第一行包括两个正整数 n、m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。
接下来 n-1 行描述航道的建设情况,其中第 i 行包含三个整数 ai, bi 和 ti,表示第
i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。
接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j个 运输计划是从 uj 号星球飞往 vj 号星球。
输出格式:
输出 共1行,包含1个整数,表示小P的物流公司完成阶段性工作所需要的最短时间。
Sample Input
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
Sample Output
11


这道题的正解其实是二分答案+树链剖分,但是这种方法比较复杂(本人不会写),所以这里讲的是另一种二分答案+树上差分的方法。
这道题看上去是一道最短路的题,其实不然,问题就在于这道题可以将一条边变为虫洞(时间代价为0),不能用常规的方法去解。
注意到本题极限数据n,m<=300000,也就是说本题至少需要O(nlogn)才能过。我们考虑二分答案,这样已经有了一个log的复杂度,我们需要想如何在O(n)时间内判断一个答案是否可行。
我们不妨令当前判断是否可行的答案为p,首先我们预处理好每个运输计划在不删去任一条边的情况下所需的时间t(注意到题目中给的图是一棵树,用dfs维护一个dis数组,每个运输计划求一个lca就能解决)。对于t>p的每个运输计划,将该运输计划经过的边的标记值+1,对于有最多标记值的边,尝试将其删掉,再判断一遍每个运输计划所用的时间,就可以得出一个答案是否可行。
对于上述的方法,我们可以用一个树上差分来实现,在标记一个运输计划事,将起点,终点的标记值+1,将起点,终点的lca的标记值-2。然后将树从下到上搜上来(就是从叶子节点搜到根节点)(用dfs的回溯实现),每经过一个点就将标记值累加,就可以得出当前点实际上被标记过几次。
(注意存边的数组要开两倍大小,我被坑惨了)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define add(u,v,uu) (to[++top]=head[u],head[u]=top,w[top]=v,val[top]=uu)
#define For(x) for (int h=head[x],o=w[h],v=val[h];h;o=w[h=to[h]],v=val[h])
#define maxn 300050
#define maxt 23
using namespace std;
int n,m,i,uu,vv,ww,ii;
int to[maxn*2],head[maxn*2],w[maxn*2],val[maxn*2],top,last[maxn];
int f[maxn][maxt],d[maxn],dis[maxn],tmp[maxn],pre[maxn]; //father,depth,distant
int st[maxn],ed[maxn],len[maxn],lca_[maxn];
int l=0,r=0,mid,res,now,maxv;
int lca(int u,int v)
{
    if (d[u]<d[v]) swap(u,v);
    for (ii=21;ii>=0;ii--)
    {
        if (d[f[u][ii]]>=d[v]) u=f[u][ii];
        if (u==v) return u;
    }
    for (ii=21;ii>=0;ii--)
    {
        if (f[u][ii]!=f[v][ii]) u=f[u][ii],v=f[v][ii];
    }
    return f[u][0];
}
inline void dfs(int x,int fa)
{
    d[x]=d[fa]+1;
    for (i=1;i<=21;i++) f[x][i]=f[f[x][i-1]][i-1];
    For(x) if (o!=fa) f[o][0]=x,dis[o]=dis[x]+v,last[o]=v,dfs(o,x);
}
int dfs2(int x,int fa)
{
    int ret=tmp[x];
    For(x) if (o!=fa) ret+=dfs2(o,x);
    if (ret==now) res=max(res,last[x]);
    return ret;
}
inline void diff(int x,int y) //difference
{
    tmp[st[x]]+=y,tmp[ed[x]]+=y,tmp[lca_[x]]-=2*y;
}
bool ok(int p)
{
    res=now=0; memset(tmp,0,sizeof(tmp));
    for (i=1;i<=m;i++) if (len[i]>p) now++,diff(i,1);
    if (!now) return 1; dfs2(1,0);
    return maxv-res<=p;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (i=1;i<n;i++) scanf("%d%d%d",&uu,&vv,&ww),add(uu,vv,ww),add(vv,uu,ww);
    dfs(1,0);
    for (i=1;i<=m;i++)
    {
        scanf("%d%d",&st[i],&ed[i]);
        len[i]=dis[st[i]]+dis[ed[i]]-2*dis[lca_[i]=lca(st[i],ed[i])]; 
        maxv=max(maxv,len[i]);
    }
    r=maxv;
    while (l<=r)
    {
        mid=(l+r)/2;
        if (ok(mid)) r=mid-1;else l=mid+1;
    }  
    printf("%d\n",l);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值