完结篇,终于完结了,所以我现在入门了?
树上倍增和树链剖分
树上倍增就是利用二进制拆分一次跳跃尽量多的距离,用一个fa数组维护每个点第2^i层父亲是谁。这样我们就可以把之前暴力每次跳到直接父亲的操作优化到每次尽可能地跳最远的2^i层父亲,从而把n优化到logn,但是倍增虽好,它只能处理离线问题,如果涉及到在线修改,在线查询那么倍增的局限性就出来了,因此就引出了下面的算法:树链剖分。
树链剖分就是把一棵树解剖成一条条链,并把其中某些链反映到线段树上维护一些链的属性。具体来说,就是把结点分为重儿子和轻儿子两类,重儿子是对于这个父亲来说子树最大(子树结点最多)的那个儿子,其余的都是轻儿子。由重儿子组成的链叫做重链,其余叫轻链。我们dfs的时候优先走重儿子,这样就能保证树上所有的重链都能反应成一个连续的区间方便我们用线段树维护,然后轻链我们就一个一个暴力地跳即可,那为什么这样复杂度不会爆呢?我们考虑跳过一个轻链的时候,由于这是一个轻链,所以我们相当于丢掉了超过一半的结点(重链的结点儿子数量是子树中最大的)因此走一条轻链丢掉一半,所以一条路径上经过的轻链的条数一定<=logn,同理,经过的重链的数量也一定<=logn,因为重链是由轻链连接的,轻链不超过logn,重链也不会超过logn。而每一条重链又可以通过线段树达到O(logn)的操作,所以总复杂度是logn*logn。
树剖还有一点要注意的就是我们第二轮重新标号的时候其实它也是一个dfn序(只是先访问的儿子不一样而已),因此每个子树反映到线段树上也是一个连续的区间,也是可以直接维护的。
树剖啥都好,就是代码太长了,一旦写爆了要调一年呜呜呜。
一般题目涉及到路径修改的都可以往树剖上想,但是不涉及到修改的一般去想倍增会好。(带修想树剖)
再bb一句,感觉树剖其实就是一种工具,难的还是怎么灵活运用线段树。
树上倍增
树上倍增求lca
为什么有st表的O(1)查询还需要倍增的O(logn)的呢?因为倍增可以维护更多的东西,st表只能求出最近公共祖先,当碰到距离这种具有可加性的属性时,我们可以用st表求出最近公共祖先然后用dep去算,但是如果是路径最大最小值这样的不具有可加性的属性时,就必须用倍增的方法了。
int dep[maxn];
int fa[maxn][31];
void dfs(int x,int f){
dep[x]=dep[f]+1;
fa[x][0]=f;
for(int i=1;i<31;i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];i!=-1;i=edge[i].ne){
int son=edge[i].to;
if(son==f){
continue;
}
dfs(son,x);
}
}
int lca(int a,int b){
if(dep[a]>dep[b]){
swap(a,b);
}
int tem=dep[b]-dep[a];
for(int j=0;tem;j++,tem>>=1){
if(tem&1){
b=fa[b][j];
}
}
if(a==b){
return b;
}
for(int j=30;j>=0&&a!=b;j--){
if(fa[a][j]!=fa[b][j]){
a=fa[a][j];
b=fa[b][j];
}
}
return fa[a][0];
}
树上倍增板子,路径最大边权,我们只需要在不向上跳跃的过程中实时地维护当前跳跃的这一段的最大边权即可,这个的维护方法就和我们维护第2^i层父亲一样,倍增地去维护就好
#include <bits/stdc++.h>
using namespace std;
#define visit _visit
#define next _next
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define int long long
#define ll long long
#define pint pair<int,int>
const int mod = 998244353;
const int maxn = 200001;
const int INF = 0x3f3f3f3f;
void read(int &x){
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x) {return quick_pow(x, mod-2);}
//----------------------------------------------------------------------------------------------------------------------//
struct e{
int a,b,v,idx,ans;
};
e es[maxn];
bool com(e a,e b){
return a.v<b.v;
}
int fa[maxn];
int find(int x){
if(fa[x]==x){
return x;
}
return fa[x]=find(fa[x]);
}
struct node{
int ne,to,val;
};
node edge[maxn<<1];
int head[maxn];
int cnt=0;
void addedge(int a,int b,int v){
edge[cnt].to=b;
edge[cnt].ne=head[a];
edge[cnt].val=v;
head[a]=cnt++;
}
int jump[maxn][21],cost[maxn][21],dep[maxn];
void dfs(int x,int f){
dep[x]=dep[f]+1;
jump[x][0]=f;
for(int i=1;i<21;i++){
jump[x][i]=jump[jump[x][i-1]][i-1];
cost[x][i]=max(cost[jump[x][i-1]][i-1],cost[x][i-1]);
}
for(int i=head[x];i!=-1;i=edge[i].ne){
int son=edge[i].to;
if(son==f){
continue;
}
cost[son][0]=edge[i].val;
dfs(son,x);
}
}
int lca(int a,int b){
int ma=0;
if(dep[a]>dep[b]){
swap(a,b);
}
int tem=dep[b]-dep[a];
for(int j=0;tem;j++,tem>>=1){
if(tem&1){
ma=max(ma,cost[b][j]);
b=jump[b][j];
}
}
if(a==b){
return ma;
}
for(int j=20;j>=0&&a!=b;j--){
if(jump[a][j]!=jump[b][j]){
ma=max(ma,cost[a][j]);
ma=max(ma,cost[b][j]);
a=jump[a][j];
b=jump[b][j];
}
}
ma=max(ma,cost[a][0]);
ma=max(ma,cost[b][0]);
return ma;
}
bool com2(e a,e b){
return a.idx<b.idx;
}
void solve(){
memset(head,-1,sizeof(head));
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
cin>>es[i].a>>es[i].b>>es[i].v;
es[i].idx=i;
}
int sum=0;
sort(es+1,es+1+m,com);
for(int i=1;i<=m;i++){
int a=es[i].a;
int b=es[i].b;
int fa1=find(a);
int fa2=find(b);
if(fa1==fa2){
continue;
}
addedge(a,b,es[i].v);
addedge(b,a,es[i].v);
sum+=es[i].v;
fa[fa1]=fa2;
es[i].ans=1;
}
dfs(1,0);
sort(es+1,es+1+m,com2);
int tem=sum;
for(int i=1;i<=m;i++){
tem=sum;
int a=es[i].a;
int b=es[i].b;
tem-=lca(a,b);
tem+=es[i].v;
cout<<tem<<endl;
}
}
signed main(){
fast;
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
树上倍增板子,路径最小边权
但是要注意图是不是联通的
#include <bits/stdc++.h>
using namespace std;
#define visit _visit
#define next _next
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define int long long
#define ll long long
#define pint pair<int,int>
const int mod = 998244353;
const int maxn = 10001;
const int maxm=5e4+5;
const int INF = 1e18;
void read(int &x){
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x) {return quick_pow(x, mod-2);}
//--------------------------------------------------------------------------------------------------------------