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;
}
标签:个点,int,边权,CF,702,vector,数组,倍增
From: https://www.cnblogs.com/fengxue-K/p/17969925