计划运输(noip2015 D2 T3)

题意:

一棵n个节点的树,对于树上的m条链

我们可以选取树上的唯一一条边使它的边权变为0

求处理后最长链的长度

思路:

二分答案:在check函数里枚举所有比二分的mid值大的询问,也就是不满足的当前猜测的答案的询问。

然后对这些询问(是一条条链)都求出公共边,如果去掉这条边可以使不满足的询问满足,就二分猜更小的值找。

找公共边:利用树上差分找被经过的边的次数,树上差分操作:对于每一条路径,将起点和终点-1,它们的lca+2,求lca的子树和即可求出lca上的一条边被经过的次数。

找lca:倍增

#include<bits/stdc++.h>
using namespace std;
const int N=1e5*3+5;
int to[N<<1],head[N],nex[N<<1],we[N<<1],fa[N],dep[N],tot=0;
int f[N][22],d[N],x[N],y[N],n,m,num[N],cnt=0,tmp[N];
struct node{
    int x,y,d;
}e[N];
struct node1{
    int x,y,lc,dis;
}b[N];
void add(int a,int b,int ww)
{ to[++tot]=b; nex[tot]=head[a]; head[a]=tot; we[tot]=ww; }
int read()
{
    int x=0,f=1;
    char s=getchar();
    while(s<'0'||s>'9')
    { if(s=='-') f=-1; s=getchar(); }
    while(s>='0'&&s<='9')
    { x=x*10+s-'0'; s=getchar(); }
    return x*f;
}
void dfs(int u)
{
    num[++cnt]=u;
    for(int i=head[u];i;i=nex[i])
    {
        int v=to[i];
        if(v==fa[u]) continue;
        fa[v]=u; dep[v]=dep[u]+1; d[v]=d[u]+we[i];//更新到根节点的距离 
        f[v][0]=u; 
        for(int i=1;i<=20;++i) f[v][i]=f[f[v][i-1]][i-1];
        dfs(v);
    }
}
int lca(int a,int b)
{
    if(dep[a]<dep[b]) swap(a,b);
    for(int i=20;i>=0;--i)
     if(dep[f[a][i]]>=dep[b])
      a=f[a][i];
    if(a==b) return a;
    for(int i=20;i>=0;--i)
     if(f[a][i]!=f[b][i])
      a=f[a][i],b=f[b][i];
    return f[a][0];
}
bool check(int mid)
{
    int ans=0,cnt=0;
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=m;i++)
    if(b[i].dis>mid)
    {//树上差分:儿子节点++ 父节点-=2 
//求任意一子树和就可以得出经过公共点上面那一条边的次数 
        tmp[b[i].x]++,tmp[b[i].y]++,tmp[b[i].lc]-=2;
        ans=max(ans,b[i].dis-mid);
        cnt++;
    }
    if(cnt==0) return true;
    //统计子树和 倒着统计是因为dfs序越大就越深 从儿子向上更新 
    for(int i=n;i>=1;--i) tmp[f[num[i]][0]]+=tmp[num[i]];
    for(int i=2;i<=n;i++)//1是根节点 不循环
    //所有询问必须满足条件所要减掉的公共边
    //减掉之后满足二分的答案 就 return true
    if(tmp[i]==cnt&&d[i]-d[f[i][0]]>=ans) return true;
    return false;
}
int main()
{
    freopen("transport.in","r",stdin);
    freopen("transport.out","w",stdout);
    int sum=0,minn=1005;
    n=read(),m=read();
    for(int i=1;i<=n-1;++i)
    {
        e[i].x=read(),e[i].y=read(),e[i].d=read();
        add(e[i].x,e[i].y,e[i].d),add(e[i].y,e[i].x,e[i].d);
        minn=max(minn,e[i].d);
    }
    dep[1]=1; dfs(1);
    for(int i=1;i<=m;i++)
    {
        b[i].x=read(),b[i].y=read();
        b[i].lc=lca(b[i].x,b[i].y);
        b[i].dis=d[b[i].x]+d[b[i].y]-2*d[b[i].lc];
        sum=max(sum,b[i].dis);
    }
    //二分思路:二分一个答案 统计出大于这个答案的所有询问 
    //考虑去掉一条边能不能使所有不满足的满足 
    //不需要枚举所有都满足 只需要使最大的减去边满足就可以了 
    int l=sum-minn-1,r=sum+1,ans;//注意二分的边界 否则会超时 
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))  r=mid,ans=mid;
        else l=mid+1;
    }
    printf("%d\n",ans);
}
/*
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
*/

 

posted on 2019-03-26 11:47  rua-rua-rua  阅读(133)  评论(0)    收藏  举报