2023ACM暑假训练day 10 树上问题
DAY 10 树上问题
训练情况简介
2023-07-08 09:51:05 星期六
训练地址:传送门
早上:H
下午:IA
晚上:DE
后补:F
树的dfs序+树的重心+LCA
H 题 dfs序+线段树维护区间大乘积(取log)
I 题 dfs序+线段树维护区间和(维护区间和这个可以推,仔细想想就行)
A 题 树的重心,怎么通过删边增边使得树的重心唯一
D 题 LCA最近公共祖先模板题
E 题 求树上一点移动向另一点移动的最终停止点,移动距离有限制,路径要求最短
F 题 多次求包含指定边的最小生成树的边权之和。蛮综合的一题,非常值得尝试
H 题
cf E. Filthy Rich Trees
题意:
给你一棵树,所有节点的初始权值为1。进行q次操作,每次操作要么将节点x的权值修改为y,要么问你,x的子树的权值之积和y的子树的权值之积的比值,比值大于1e9是输出1e9
思路:
找任意节点的子树,考虑dfs序,将子树变为区间,再利用线段树维护乘积,直接维护乘积数字过大,所以维护乘积的log,输出时取10的幂次即可
有意思,有空可以再试试
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
呜呜呜,写了半天,发现题目看错了。。。
区间乘积?线段树维护吧
但是维护乘积,会爆吧,取个对数?对10取对数
线段树维护log区间和
! update的时候没取log,可能是wa的原因
solve里面query下标写错了,,,
小问题蛮多的哈,下次注意!
*/
const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,q,head[maxm],cnt=1,l[maxm],r[maxm],xu=0;
double seg[maxm<<2];
struct node{
int to,next;
}p[maxm<<1];
void add_edge(int a,int b){
p[cnt].to=b;
p[cnt].next=head[a];
head[a]=cnt++;
return ;
}
void dfs(int x,int fa){
l[x]=++xu;
for(int i=head[x];i;i=p[i].next){
if(p[i].to==fa) continue;
dfs(p[i].to,x);
}
r[x]=xu;
return ;
}
int ls(int p) { return p<<1; }
int rs(int p) { return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
seg[p]=0;//lg(1)=0 因为初始v=1
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
return ;
}
void update(int pos,double k,int p,int pl,int pr){
if(pl==pr){
seg[p]=k;
return ;
}
int mid=(pl+pr)>>1;
if(pos<=mid) update(pos,k,ls(p),pl,mid);
else update(pos,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
double query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r){ return seg[p]; }
int mid=(pl+pr)>>1;
double res=0;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(mid<r) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
void solve(){
cin>>n;
int a,b;
for(int i=0;i<n-1;++i){
cin>>a>>b;
add_edge(a,b);
add_edge(b,a);
}
dfs(1,0);
build(1,1,n);
cin>>q;
int op;
while(q--){
cin>>op;
if(op==1){//update
int x;double y;
cin>>x>>y;
update(l[x],log10(y),1,1,n);
}else{//compare
int x,y;
cin>>x>>y;
double xx,yy;
xx=query(l[x],r[x],1,1,n);
yy=query(l[y],r[y],1,1,n);
double d=xx-yy;
if(d>=9.0) cout<<"1000000000\n";
else cout<<fixed<<setprecision(10)<<pow(10,d)<<'\n';
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
I 题
cf F. The Lorax
题意:
给你一棵树,n个节点,n-1条边,所有节点均包含两个属性:seed和pot,初始两个属性均为0。共q次操作,每次操作要么让你给a节点加c个seed,给b节点加c个pot;或者问你对于某一边的两个端点a和b,在满足所有seed与pot一一匹配且匹配总距离最短的情况下(任意两节点之间的距离就是最短的路径长度),问你必须经过当前边的匹配对数。
思路:
就是说,如果对于两个点,以直接将他俩连接的边作为分界线,将给定的树划分为两棵树,左树右树里面均有seed和pot。如果说左树里面的seed与pot已经匹配上了,没有剩余,那么就不需要跨越边界去另一棵树匹配;反之,如果匹配不上,那么就需要跨越边界去寻找,而求的就是这个跨越边界的seed和pot的对数。
英文题面还是有点吃力~~~
其实题目并不难,分析之后我们可以发现,就是在单一的一边找abs(seed-pot)。对吧!因为是成对添加的,所以匹配不上就要跨越,而匹配不上就是答案。为此,我们可以令+1个seed为节点当前权值+1,+1个pot为当前权值-1,那么,当询问时,我们只需要统计一棵子树的权值和即可。
这可以利用dfs序+线段树快速求解。
那么最后一个问题,子树怎么确定呢?或者说,该怎么求和呢?对于给定的边端点a和b,我们可以知道,深度深的那个端点,也就是dfs序后的端点,被划分为一棵子树,剩下的作为另一棵子树,那么我们的深的那棵子树求权值和即可。
详见代码:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
不知道错在什么地方了,我感觉是想法错了
其实仅考虑就是给你的ab一定是一条边的两个点
所以,你找dfs序靠后的位置,找它的子树里面的未匹配数就行
另外,x~1e8别忘了long long,也别忘了相关的函数返回值也要改long long
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,q,cnt,head[maxm],l[maxm],r[maxm];
ll seg[maxm<<2];
struct edge{
int to,next;
}p[maxm<<1];
void pre(){
cnt=1;
for(int i=0;i<=n;++i) head[i]=0;
return ;
}
void add_edge(int a,int b){
p[cnt].to=b;
p[cnt].next=head[a];
head[a]=cnt++;
return ;
}
void dfs(int x,int fa){
l[x]=++cnt;
for(int i=head[x];i;i=p[i].next){
if(p[i].to==fa) continue;
dfs(p[i].to,x);
}
r[x]=cnt;
return ;
}
int ls(int p) { return p<<1; }
int rs(int p) { return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
seg[p]=0;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int pos,ll k,int c,int p,int pl,int pr){
if(pl==pr){
if(c==0) seg[p]+=k;
else seg[p]-=k;
return ;
}
int mid=(pl+pr)>>1;
if(pos<=mid) update(pos,k,c,ls(p),pl,mid);
else update(pos,k,c,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
ll mid=(pl+pr)>>1,ans=0;
if(l<=mid) ans+=query(l,r,ls(p),pl,mid);
if(mid<r) ans+=query(l,r,rs(p),mid+1,pr);
return ans;
}
void solve(){
cin>>n>>q;
pre();
int a,b;
for(int i=0;i<n-1;++i){
cin>>a>>b;
add_edge(a,b);
add_edge(b,a);
}
cnt=0;
dfs(1,0);
build(1,1,n);
ll c;
while(q--){
cin>>a>>b>>c;
if(c==0){//query
int y;
if(l[a]>l[b]) y=a;
else y=b;
cout<<abs(query(l[y],r[y],1,1,n))<<'\n';
}else{//add
update(l[a],c,0,1,1,n);
update(l[b],c,1,1,1,n);
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
A 题
cf 1406 C. Link Cut Centroids
题意:
给你一棵树,n个顶点,n-1条边。问你通过先删边再增边的操作,使得最后的树的重心唯一,增删的边可以相同
思路:
对于只有唯一重心的树,随意删增同一条边就行。
对于有2个重心的树,这两个重心一定由一条边相连,则我们只需要改变一个重心的最大子树节点数即可,这样就能使得重心唯一。选一个重心的一个叶子连在另一个重心上即可。自己的代码是将一个重心的一个子树连在另一个重心上,不知是否有反例
下为代码:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
通过加边删边的操作使得树的重心唯一?
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,head[maxm],cnt,d[maxm],len;
vector<int> e;
struct node{
int to,next;
}p[maxm<<1];
void add_edge(int a,int b){
p[cnt].to=b;
p[cnt].next=head[a];
head[a]=cnt++;
return ;
}
int dfs(int x,int fa){
d[x]=1;
int t,Max=0;
for(int i=head[x];i;i=p[i].next){
if(p[i].to==fa) continue;
t=dfs(p[i].to,x);
d[x]+=t;
Max=max(Max,t);
}
Max=max(Max,n-d[x]);
if(Max<len){
len=Max;
e.clear();
e.push_back(x);
}else if(Max==len){
e.push_back(x);
}
return d[x];
}
void solve(){
mem(head,0);
mem(d,0);
e.clear();
cnt=1;
cin>>n;
len=n+1;
int a,b;
for(int i=1;i<n;++i){
cin>>a>>b;
add_edge(a,b);
add_edge(b,a);
}
dfs(1,0);
if(e.size()==1){//一个重心,随便增删
int t=e[0],u=p[head[t]].to;
cout<<t<<" "<<u<<'\n'
<<t<<' '<<u<<'\n';
}else{//两个重心,改变一个重心的最大子树节点数,但有没有反例?
int x=e[0],y=e[1],z;
for(int i=head[x];i;i=p[i].next){
if(p[i].to==y) continue;
else{
z=p[i].to;
break;
}
}
cout<<z<<' '<<x<<'\n'
<<z<<' '<<y<<'\n';
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
D 题
洛谷 P3379 【模板】最近公共祖先(LCA)
题意:
求树上指定两个点直接最近的公共祖先
思路:
LCA模板题,算法过程详见自己的摘记
下为代码:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
最近公共祖先模板题
利用深搜构造lca数组
*/
const int maxm=5e5+5,inf=0x3f3f3f3f,mod=998244353,N=20;
int n,m,s,cnt=1,head[maxm],dep[maxm],fa[maxm][N+1];
struct node{
int to,next;
}p[maxm<<1];
void add_edge(int a,int b){
p[cnt].to=b;
p[cnt].next=head[a];
head[a]=cnt++;
return ;
}
void dfs(int x,int f){
dep[x]=dep[f]+1;
fa[x][0]=f;
for(int i=1;i<=N;++i){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];i;i=p[i].next){
if(p[i].to!=f) dfs(p[i].to,x);
}
return;
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=N;i>=0;--i){
if(dep[fa[u][i]]>=dep[v]){
u=fa[u][i];
}
}
if(u==v) return v;
for(int i=N;i>=0;--i){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[v][0];
}
void solve(){
cin>>n>>m>>s;
int a,b;
for(int i=1;i<n;++i){
cin>>a>>b;
add_edge(a,b);
add_edge(b,a);
}
dfs(s,0);
while(m--){
cin>>a>>b;
cout<<lca(a,b)<<'\n';
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
E 题
cf C. Sloth Naptime
题意
就是给你一棵树,一共q次询问,每次询主人的树懒位于节点a,它具有c的能量最多能跨越c条边,主人想让其到达节点b。如果可以抵达节点b,树懒就会待在节点b;如果不能到达,那么树懒会待在离节点b最近的地方。每次询问让你输出树懒最终停留的位置。
思路
考虑a和b的最近公共祖先m,最短路径长为$len=sep[a]+dep[b]-dep[m] \times 2 $ ,则有如下关系:
1.$ c\ge len $ 时,可以抵达,最终结果为b
2.$ c<len $ 时,不可以抵达b,那么它最后一定在a与m或者b与m最短路径上。
当\(c<dep[a]-dep[m]\)时,它最后一定在a与m上;反之,它最后一定在b与m上。
而具体的位置,可以利用fa数组求解
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353,N=20;
int n,q,cnt=1,head[maxm],dep[maxm],fa[maxm][N+1];
struct node{
int to,next;
}p[maxm<<1];
void add_edge(int a,int b){
p[cnt].to=b;
p[cnt].next=head[a];
head[a]=cnt++;
return ;
}
void dfs(int x,int f){
dep[x]=dep[f]+1;
fa[x][0]=f;
for(int i=1;i<=N;++i){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];i;i=p[i].next){
// debug(p[i].to);
if(p[i].to!=f)
dfs(p[i].to,x);
}
return ;
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=N;i>=0;--i){
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
}
if(u==v) return v;
for(int i=N;i>=0;--i){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[v][0];
}
int swimming(int x,int len){
int p=1;
while(len){
if(len&1) x=fa[x][p-1];
len>>=1;
++p;
}
return x;
}
void solve(){
cin>>n;
int a,b,c;
for(int i=1;i<n;++i){
cin>>a>>b;
add_edge(a,b);
add_edge(b,a);
}
dfs(1,0);
cin>>q;
while(q--){
cin>>a>>b>>c;
if(a==b){
cout<<a<<'\n';
continue;
}
int m=lca(a,b),len=dep[a]+dep[b]-dep[m]*2;
if(len<=c) cout<<b<<'\n';
else{
len-=c;
if(c>=dep[a]-dep[m]){
cout<<swimming(b,len)<<'\n';
}else{
cout<<swimming(a,c)<<'\n';
}
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
F 题
cf 609 E. Minimum spanning tree for each edge
题意:
给你一个无自环和重边的图,对于图中的每一条边,求其最小生成树
思路:
朴素思想,每一条边均求一次最小生成树,应该会超时
我们先考虑原图存在的最小生成树。如果说问到某条包含在最小生成树中的边时,直接输出该图的最小生成树的边权之和即可;但问到不在最小生成树中的边时,我们将这条边加入所得的最小生成树就会得到一个环,我们去掉当前环上的权值最大的一条边(非指定边),所得即可包含新的指定边的最小生成树(证明?)
先求出最小生成树:对于在最小生成树中的边,答案就是最小生成树的边权和;对于不在最小生成树中的边(u, v),如果我们把这条边加入到最小生成树中,我们会得到一个环,我们如果去掉在原最小生成树上 u 到 v 的路径上的边权最大的边即可得到包含边 (u, v) 的最小生成树。
那么问题来了,怎么找那条权值最大的边?新学的lca给你这个帮助。应该很容易发现,求新加一边两端点的lca时所遍历的点就是在最小生成树上形成环的点。那么我们就可以利用lca缩短我们寻找最大值所需要的时间。这里使用st表辅助,因为倍增和st表的构造 什么“一拍即合!”
整体过程就是:先kruskal或prim求最小生成树,再dfs构造树上lca和st表,再对于每一条边利用lca求得去掉的最大值,最后输出答案即可
下为代码:
这份代码写的有些赘余了,有空再优化一下[ ]
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
最小生成树+LCA
吐血,错了半天竟然是因为一个字母~
*/
const int maxm=2e5+5,inf=0x3f3f3f3f,mod=998244353,N=20;
int n,m,cnt=1,fa[maxm];
ll max_ele[maxm][N+1],w;
int f[maxm][N+1],dep[maxm];
bool vis[maxm];
struct node{
ll v,w;
node(ll a,ll b){
v=a;w=b;
}
};
vector<node> e[maxm];
struct edge{
ll u,v,w;
bool operator < (const edge & a)const {
return a.w<w;
}
}p[maxm];
priority_queue<edge> q;
void pre(){//预处理并查集
for(int i=0;i<=n;++i) fa[i]=i;
return ;
}
int findfa(int x) { return fa[x]==x?x:fa[x]=findfa(fa[x]); }
void kruskal(){//求最小生成树
w=0;
int u,v,a,b;
while(!q.empty()){
edge t=q.top();
q.pop();
a=findfa(t.u);
b=findfa(t.v);
if(a==b) continue;
else{
fa[b]=a;
w+=t.w;
e[t.u].push_back(node(t.v,t.w));
e[t.v].push_back(node(t.u,t.w));
}
}
return ;
}
void dfs(int x,int fath,ll val){//dfs求倍增lca,同时维护一个最大值st表
dep[x]=dep[fath]+1;
f[x][0]=fath;
vis[x]=true;
max_ele[x][0]=val;
for(int i=1;i<=N;++i){
f[x][i]=f[f[x][i-1]][i-1];
max_ele[x][i]=max(max_ele[x][i-1],max_ele[f[x][i-1]][i-1]);
}
for(int i=0;i<e[x].size();++i){
if(!vis[e[x][i].v])
dfs(e[x][i].v,x,e[x][i].w);
}
return ;
}
ll lca(int u,int v){//利用lca和st表找最大值,倍增刚好符合st表的条件
ll res=0;
if(dep[u]<dep[v]) swap(u,v);
for(int i=N;i>=0;--i){
if(dep[f[u][i]]>=dep[v]){
res=max(res,max_ele[u][i]);
u=f[u][i];
}
}
if(u==v) return res;
for(int i=N;i>=0;--i){
if(f[u][i]!=f[v][i]){
res=max(res,max_ele[u][i]);
res=max(res,max_ele[v][i]);
u=f[u][i];
v=f[v][i];
}
}
res=max(res,max_ele[u][0]);
res=max(res,max_ele[v][0]);
return res;
}
void solve(){
cin>>n>>m;
pre();
for(int i=0;i<m;++i){
cin>>p[i].u>>p[i].v>>p[i].w;
q.push(p[i]);
}
kruskal();
dfs(1,0,0);
for(int i=0;i<m;++i){
ll t=lca(p[i].u,p[i].v);
cout<<w-t+p[i].w<<'\n';
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17536669.html