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;
}
posted on 2023-07-08 09:52  Qiansui  阅读(23)  评论(0编辑  收藏  举报