前缀和+最近公共祖先解决景区导游

题目来自Dotcpp:

前缀和+最近公共祖先解决景区导游_第1张图片

前缀和+最近公共祖先思路:

这道题目之前用暴力做,只能得到43分,时间复杂度太高了。我们需要优化,就要用到预处理-前缀和。

前缀和思路就是将每个点到起点距离要花费的时间都记录在一个数组 sum 中,我们得到前缀和之后,就可以解决题目。模拟一下,当计算跳过2这个点,我们可以先计算不跳过点需要的总时间ans,然后再减去跳过2这个点时间:ans -= sum[2] + sum[6] - 2*(sum[2和6的最近公共祖先]),就是不经过2要花费的总时间。同样的,跳过最后景点我们也可以这样计算,但是,在计算跳过中间景点的时候,我们还要加上(假设跳过景点为6)ans += sum[2] + sum[5] - 2*(sum[2和5最近公共祖先])。

那么最近公共祖先怎么计算呢?网上很多模板,也有很多视频可以学习。

代码如下:

#include
#define int long long
using namespace std;
const int N = 1e5+10;

int n, k;
typedef pair PII;//存路线 1-2……
vectorg[N]; //存路线、时间
int arr[N]; //游览线路
//最近公共祖先
int fa[N], son[N], sz[N], dep[N], top[N];
//预处理|前缀和
int sum[N];
//计算最近公共祖先
//确定一个节点的重儿子
void dfs1(int u, int father){
    fa[u] = father; dep[u] = dep[father]+1; sz[u] = 1;
    for(int i = 0; i < (int)g[u].size(); i++){
        int edge = g[u][i].first;
        if(edge == fa[u]) continue;
        dfs1(edge, u);
        sz[u] += sz[edge];
        if(sz[son[u]] < sz[edge]) son[u] = edge;
    }
}
//确定每个点的链头
void dfs2(int u, int t){
    top[u] = t;
    if(!son[u]) return;
    dfs2(son[u], t);
    for(int i = 0; i < (int)g[u].size(); i++){
        int v = g[u][i].first;
        if(v == son[u] || v == fa[u])continue;
        dfs2(v, v);
    }
}
//得到两个点的最近公共祖先
int lca(int u, int v){
    while(top[u]!=top[v]){
        if(dep[top[u]] < dep[top[v]]){
            swap(u,v);
        }
        u = fa[top[u]];
    }
    return dep[v] < dep[u]?v:u;
}
//计算一个点的到1要花费的总时间,并且存到sum数组中
void cal_sum(int u){
    for(int i = 0; i < (int)g[u].size(); i++){
        int v = g[u][i].first;
        int w = g[u][i].second;
        if(v == fa[u]) continue;
        sum[v] = sum[u] + w;
        cal_sum(v);
    }
}

signed main(){
    cin >> n >> k;
    for(int i = 1; i < n; i++){
           int a, b, c; cin >> a>> b>>c;
           //存路线、时间 g[1] = {2,1}
           //表示2到1花费时间1
           g[a].push_back({b,c});
           g[b].push_back({a,c});
    }
    for(int i = 1; i <= k; i++){
        cin  >> arr[i];
    }
    
    //求两个点的公共祖先
    dfs1(1,0);
    dfs2(1,1);
    
    //预处理 前缀和
    cal_sum(1);
    
    //计算游览线路花费的总时间
    int ans = 0;
    for(int i = 1; i < k; i++){
        ans += sum[arr[i]] + sum[arr[i+1]] - sum[lca(arr[i], arr[i+1])]*2;
    }
    
    //在分别计算跳过每个点要减少的时间
    for(int i = 1; i <= k; i++){
        int ans1 = ans;
        if(i == 1){
            ans1 -= sum[arr[i]] + sum[arr[i+1]] - sum[lca(arr[i], arr[i+1])]*2;
        }
        else if(i == k){
            ans1 -= sum[arr[i-1]] + sum[arr[i]] - sum[lca(arr[i-1], arr[i])]*2;
        }
        else{
            //模拟 i=2, 跳过 6 这个点
            //先减去 2-6 要花费的时间 再减去 6-5花费的时间
            //最后加上 2-5 要花费的时间
            ans1 -= sum[arr[i-1]] + sum[arr[i]] - sum[lca(arr[i-1], arr[i])] *2;
            ans1 -= sum[arr[i]] + sum[arr[i+1]] - sum[lca(arr[i], arr[i+1])] *2;
            ans1 += sum[arr[i-1]] + sum[arr[i+1]] - sum[lca(arr[i-1], arr[i+1])] *2;
        }
        cout << ans1 << ' ';
    }
    return 0;
}

你可能感兴趣的:(蓝桥杯,dfs)