运输计划 lca+树上差分/前缀和+二分

链接https://www.acwing.com/problem/content/description/523/

思路

一个涉及了一堆知识点的老复杂的题目了。lca+树上差分+二分

首先看数据范围是3e5,那么想一下怎么可以把复杂度降到 l o g ( n ) log(n) logn

题目中要求的是完成m个运输计划所需的最短的时间。即m条路径中的最大值最小,那么就需要在出m个要求的路径中,找出两个点间重复的那段权值最大。

然后发现这个时间是满足单调性的,可以想到对答案二分。设时间为mid,其实这里的时间就是路径的长度。那么对于每次的mid值,只需要检查是否能满足去掉两个点间的权值可以让剩余部分路径长度小于mid。

那么在这m条路径中,如果有t条路径长度大于mid,则归0的路径被包含在其中。

对路径中每段每次经过时+1,要是某段路径被加到t,那么它就是要被删的。

但是对路径一次一次的操作未免时间复杂度也太大了,需要优化一下,快速的对某段同时加上某一个数和求两个点间的距离。这里用线段树、树状数组或者是差分,前缀和都可以,都可以实现每次O(1)的修改和查询。

关于树上差分和树上前缀和可以看这个大佬的博客:
https://www.luogu.com.cn/blog/Sweetlemon/s-and-d-on-a-tree

然后树上差分,需要求一下最近公共祖先。用lca,tarjan或者倍增都可以,因为我不会tarjan所以我选择倍增。

然后写出来的只能跑过部分点,改了巨久也不知道错哪了orz,等一个大佬帮忙de个bug
在这里插入图片描述

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 400005;
const int M = 25;
#define INF 0x3f3f3f3f
int dep[maxn],pa[M][maxn];
int sum[maxn],dis[maxn],seq[maxn],cnt = 0;
int flca[maxn];

struct edge
{
    
    
    int u,v,t;
    void add(int a,int b,int c){
    
    
        u = a;
        v = b;
        t = c;
    }
}e[maxn];

vector<edge> g[maxn];
int n,m,a,b,c;

void dfs(int u,int fa,int d)
{
    
    
    seq[++cnt] = u;
    // cout<<"u"<<u<<endl;
    pa[0][u] = fa;
    dep[u] = d;
    for(int i = 0;i < g[u].size(); i++){
    
    
        int vv = g[u][i].v;
        if(vv == fa) continue;
        dis[vv] = dis[u] + g[u][i].t;
        dfs(vv,u,d + 1);
    }
}

void init()
{
    
    
    dfs(1,-1,0);
    for(int i = 0 ;i < M; i++){
    
    
        for(int v = 1 ;v <= n; v++){
    
    
            if(pa[i][v] == -1) pa[i + 1][v] = -1;
            else pa[i + 1][v] = pa[i][pa[i][v]];
        }
    }
}

int lca(int u,int v)
{
    
    
    if(dep[u] > dep[v]) swap(u,v);
    for(int k = 0; k < M ;k++){
    
    
        if(dep[v] - dep[u] >> k & 1)
            v = pa[k][v];
    }
    if(u == v) return u;
    for(int k = M - 1; k>= 0; k--){
    
    
        if(pa[k][u] != pa[k][v]){
    
    
            u = pa[k][u];
            v = pa[k][v];
        }
    }
    return pa[0][u];  
}

bool check(int mid)
{
    
    
    int s = 0;
    memset(sum,0,sizeof(sum));
    int ma = 0;
    for(int i = 0;i < m; i++){
    
    
        int v = e[i].v,u = e[i].u;
        int l = e[i].t;
        if(l > mid){
    
     
            ma = max(ma,l);//路径的最大值
            sum[v] += 1,sum[u] += 1;
            sum[flca[i]] -= 2; 
            s++;
        }
    }
    // cout<<"s"<<s<<endl;
    if(!s) return true;

    //dfs序
    for(int i = n;i >= 1; i--){
    
    
        int ne = seq[i];
        sum[pa[0][i]] += sum[ne];
        // cout<<"ne"<<ne<<endl;
    }
    
    for(int i = 1;i <= n; i++){
    
    
        if(sum[i] == s && mid + dis[i] - dis[pa[0][i]] >= ma)
            // cout<<"s"<<s<<endl;
            return true;
        
    }
    return false;
}

int main()
{
    
    
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    memset(pa,-1,sizeof(pa));
    cin>>n>>m;
    for(int i = 0;i < n - 1 ; i++){
    
    
        cin>>a>>b>>c;
        edge e1,e2;
        e1.add(a,b,c);
        e2.add(b,a,c);
        g[a].push_back(e1);
        g[b].push_back(e2);
    }
    init();
    int u,v,ll;
    for(int i = 0;i < m ;i++){
    
    
        cin>>u>>v;
        flca[i] = lca(u,v);
        ll = dis[v] + dis[u] - 2 * dis[flca[i]];
        e[i].add(u,v,ll);
        // cout<<flca[i]<<endl;
    }

    int l = 0,r = 1e9;
    while(l < r)
    {
    
    
        int mid = l + r >> 1;
        // cout<<mid<<endl;
        if(check(mid))  r = mid;
        else l = mid + 1;
    }
    cout<<r<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u011612364/article/details/115270732