洛谷P2680 运输计划(LCA+树上差分)

首先考虑如果一段运输可以在t的时间内完成,那么一定是可以在大于t的时间内完成的,服从了单调性,于是我们这里可以考虑一下二分查找距离最大话重复的边。

我们假设时间确定,原问题变成一个判定性问题:是否可以通过去掉一条边,使所有路径的总长度在 t以内。

此时去掉所有长度大于 t的路径上的最长公共边一定是最优的。

那怎么找出所有公共边呢?我们可以考虑差分,将每条路径上的所有边加1,那么经一次这条边就会加1,假设一共有s条路径,如果有重边,那么每走一次都会经过他一遍,最后经过了s次,于是只需要判断每条边上的总和是否等于路径总数即可。

那么我们的问题是就变成了求树上某一条路径的和,以及给某个路径加上相同数呢。

这里可以利用前缀和还有差分。

对于距离,假设要求x,y之间的距离,我们先预处理出每个点到根节点的距离dis[x],然后再利用x到y的距离一定是经过了最近公共祖先的,于是由前缀和

d(x,y)=dis[x]-dis[lca(x,y)]+dis[y]-dis[lca(x,y)],求LCA的方式有很多,这里采用的是倍增的方式。

对于每条路径加上相同数,我们利用差分,设差分数组为sum[x](表示的是1到x),那么要把x,y的路径都加上1,就相当于把x到lca(x,y)加上1和lca(x,y)到y之一段加上1,于是sum[x]++,sum[y]++,但是sum表示的是由于是根节点到x,于是我们刚刚多加了两次从根节点到lca(x,y),于是最后还需sum[lca(x,y)]-2。

最后就二分中的Check函数,怎么写呢,就是看能不能找到至少一条长为k公共边,使得最长链的长度max(length-k)<=t.

 

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int h[N],ne[N*2],e[N*2],w[N*2],idx;
int dep[N],f[N][22],dis[N];
int sum[N];//差分数组
pair<int,int> tran[N];//用于存储从x运输到y的位置
int blca[N],seq[N];//用于存储lca(x,y),记录下行走路径
int n,m,cnt;


void add(int a,int b,int c)
{
    e[++idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx;
}

void dfs(int u,int fa)//倍增以及dep和dis的预处理
{
    seq[++cnt]=u;
    dep[u]=dep[fa]+1;f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    
    for(int i=h[u];i;i=ne[i])
    {
        int v=e[i];
        if(v==fa)continue;
        dis[v]=dis[u]+w[i];
        dfs(v,u);
    }
}

int lca(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    for(int i=20;i>=0;i--)
        if(dep[f[u][i]]>=dep[v])
            u=f[u][i];
    if(u==v)return u;
    for(int i=20;i>=0;i--)
        if(f[u][i]!=f[v][i])
            u=f[u][i],v=f[v][i];
    return f[u][0];
}

bool check(int mid)
{
    memset(sum,0,sizeof sum);//每次都要用到
    int maxd=0,s=0;
    for(int i=1;i<=m;i++)
    {
        int x=tran[i].first,y=tran[i].second;
        int p=blca[i];
        int d=dis[x]+dis[y]-2*dis[p];//x和y之间的路径
        if(d>mid)//如果他们之间的大于我们规定的长度说明是要进行处理的
        {
            sum[x]++,sum[y]++;
            sum[p]-=2;//差分处理
            maxd=max(maxd,d-mid);//d-mid相当于我们给定mid,实际要有d,d-mid是需要我们处理的        
            s++;
        }
    }
    if(!s)return true;//如果s为0说明没有重边
    for(int i=n;i;i--)
    {
        int x=seq[i];
        sum[f[x][0]]+=sum[x];//构造差分数组,即把x点++的扩散到整个路径上
    }
    for(int i=1;i<=n;i++)
        if(sum[i]==s&&dis[i]-dis[f[i][0]]>=maxd)//有重边,并且这条边的路径比我们想要删除的大
            return true;//我们找到了这个
    return false;
}


int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int a,b,c;cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
    {
        int a,b;cin>>a>>b;
        tran[i]={a,b};blca[i]=lca(a,b);
    }
    
    int l=0,r=1e9;//二分最大的那个需要删除了路径
    while(l<r)
    {
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }    
    cout<<r;
}

 

posted @ 2023-12-23 18:16  yzy001  阅读(16)  评论(0)    收藏  举报