CF-702-E-倍增

702-E 题目大意

\(n\)个点,每个点有一条出边,边带权。给定整数\(k\)。求从每个节点出发经过\(k\)条边的路径上所有的边权和,以及最小的边权。


Solution

给定的图是基环树森林,从任意一个点出发无限走下去一定会进入环内。

倍增板子题,这里不详细解释什么是倍增数组,具体的可以网上自行学习。这里用到三个倍增数组:

  • \(to[i][j]\):从第\(i\)个点出发走\(2^j\)步到达的顶点。
  • \(sum[i][j]\):从第\(i\)个点出发走\(2^j\)步,路径上的边权和。
  • \(mn[i][j]\):第\(i\)个点出发走\(2^j\)步,路径上最小的边权。

倍增数组的构建,核心思路就是公式

\[2^{j}=2^{j-1}+2^{j-1} \]

\(to\)数组为例,当前我们已将所有点走过\(2^{j-1}\)步的数组预处理完毕:

\[to[i][j]=to[to[i][j-1]][j-1] \]

从第\(i\)点出发先走\(2^{j-1}\)步到达\(to[i][j-1]\)再走\(2^{j-1}\)即到达目标点。

查询节点\(i\)\(k\)条边后的边权和与最小边权:
由高到低枚举\(k\)的二进制位,如果\(k\)的第\(j\)位为\(1\),则从当前点跳到\(to[i][j]\),同时更新所求的路径和以及最小的边权。

时间复杂度\(O(nlogk)\)

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

void solve(){
    int n;
    ll k;
    cin>>n>>k;
    int P=35;
    vector<vector<int>> to(n+1,vector<int>(P));
    vector<vector<ll>> sum(n+1,vector<ll>(P));
    vector<vector<int>> mn(n+1,vector<int>(P,1e9));
    for(int i=0;i<n;i++){
        int j;
        cin>>j;
        to[i][0]=j;
    }
    for(int i=0;i<n;i++){
        int w;
        cin>>w;
        sum[i][0]=mn[i][0]=w;
    }
    for(int j=1;j<P;j++){
        for(int i=0;i<n;i++){
            to[i][j]=to[to[i][j-1]][j-1];
            sum[i][j]=sum[i][j-1]+sum[to[i][j-1]][j-1];
            mn[i][j]=min(mn[i][j-1],mn[to[i][j-1]][j-1]);
        }
    }
    for(int i=0;i<n;i++){
        ll path_sum=0;
        int path_mn=1e9,x=i;
        for(int i=P-1;~i;i--){
            if(k&(1LL<<i)){
                path_sum+=sum[x][i];
                path_mn=min(path_mn,mn[x][i]);
                x=to[x][i];
            }
        }
        cout<<path_sum<<" "<<path_mn<<'\n';
    }
}

int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
posted @ 2024-01-17 14:15  fengxue-K  阅读(21)  评论(0)    收藏  举报