树上倍增的应用
归根到底,树上倍增的应用就是求LCA
RMQ + 树上深搜
int fa[N][20];
int toUsed = 1;
struct node{
int to;
int nxt;
}p[N*2];
void mkedge(int u,int v){
p[toUsed].to = v;
p[toUsed].nxt = head[u];
head[u] = toUsed++;
p[toUsed].to = u;
p[toUsed].nxt = head[v];
head[v] = toUsed++;
}
void mkTree(int rt,int pre){
fa[rt][0] = pre;
dep[rt] = dep[pre] + 1;
for(int i=1;i<20;++i)
fa[rt][i] = fa[fa[rt][i-1]][i-1];
for(int i=head[rt];i!=-1;i=p[i].nxt){
int sn = p[i].to;
if(sn==pre) continue;
mkTree(sn,rt);
}
}
一、树上差分
树上差分: 就是在大量修改点对之间路径上边的权值(路径修改,同加),修改两个端点的lca的向上边-2*val,u,v两点向上边+val,找lca,RMQ记录祖先ST表
EOJ 3631
题意: n次路径修改,有m-1个值将要赋给 m-1条树边,问赋值后使得每条边的修改次数*边权之和最小,求最小值。
树上差分得到所有边的修改次数,将权值和修改次数排序,也就是修改次数最多的边匹配最小权值!加了一点贪心思维!
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <set>
#include <map>
#include <algorithm>
#include <string.h>
#define llt long long
using namespace std;
const int N = 2e5+777;
int w[N],head[N],val[N],dep[N];
int fa[N][20];
int toUsed = 1;
struct node{
int to;
int nxt;
}p[N*2];
void mkedge(int u,int v){
p[toUsed].to = v;
p[toUsed].nxt = head[u];
head[u] = toUsed++;
p[toUsed].to = u;
p[toUsed].nxt = head[v];
head[v] = toUsed++;
}
void mkTree(int rt,int pre){
fa[rt][0] = pre;
dep[rt] = dep[pre] + 1;
for(int i=1;i<20;++i)
fa[rt][i] = fa[fa[rt][i-1]][i-1];
for(int i=head[rt];i!=-1;i=p[i].nxt){
int sn = p[i].to;
if(sn==pre) continue;
mkTree(sn,rt);
}
}
void dfs(int rt,int pre){
for(int i=head[rt];i!=-1;i=p[i].nxt){
int sn = p[i].to;
if(sn == pre) continue;
dfs(sn,rt);
val[rt] += val[sn];
}
}
void modify(int a,int b){
val[a] += 1;
val[b] += 1;
//int lca; val[lca] -=2
if(dep[a]<dep[b]) swap(a,b);
/*将a上升到与b同样的高度*/
int h = dep[a] - dep[b];//高度差
for(int i=0;(1<<i)<=h;++i)
if(h&(1<<i)) a = fa[a][i];//前进2^i步
//cout<<a<<" "<<b<<endl;
for(int i=20-1;i>=0;--i){
if(fa[a][i]==fa[b][i]) continue;
a = fa[a][i];
b = fa[b][i];
}
if(a!=b) a=fa[a][0];
val[a] -= 2;
}
int main(){
int n;
scanf("%d",&n);
memset(head,-1,sizeof head);
memset(val,0,sizeof val);
memset(fa,0,sizeof fa);
for(int i=2;i<=n;++i){
int u,v;
scanf("%d%d%d",&u,&v,&w[i]);
mkedge(u,v);
}
dep[0] = -1;
mkTree(1,0);
int q;
scanf("%d",&q);
while(q--){
int a,b;
scanf("%d%d",&a,&b);
modify(a,b);
}
dfs(1,0);
sort(val+2,val+n+1);
sort(w+2,w+n+1);
llt ans = 0;
for(int i=n;i>=2;i--)
ans += 1LL*val[i]*w[n-i+2];
printf("%lld\n",ans);
}
二、求连续编号节点的LCA
最近的热身赛碰到的一道好题,就是求节点的编号为连续[L,R]的最近公共祖先lca!明显树上倍增求lca!然后RMQ记录已id为区间左端点长度为2次幂的编号连续的节点的lca预处理得出。然后应对询问
lca[l][2k]=LCA(lca[l][2k−1],lca[l+2k−1][2k−1])
l
c
a
[
l
]
[
2
k
]
=
L
C
A
(
l
c
a
[
l
]
[
2
k
−
1
]
,
l
c
a
[
l
+
2
k
−
1
]
[
2
k
−
1
]
)
`ural 2109
题意: 给一棵树,q次询问节点[L,R]的lca。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <map>
#include <cstdlib>
#define lson (rt<<1)
#define rson ((rt<<1)+1)
using namespace std;
typedef long long ll;
const int N = 2e5+777;
inline int read() {
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
} while('0' <= ch && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
} return x * f;
}
struct edge{
int to;
int nxt;
}ed[N*2];
int toUsed ;
int head[N];
void add_edge(int u,int v){
ed[toUsed].to = v;
ed[toUsed].nxt = head[u];
head[u] = toUsed++;
ed[toUsed].to = u;
ed[toUsed].nxt = head[v];
head[v] = toUsed++;
}
int fa[N][22],dep[N];
int lca[N][22];
int n;
void dfs(int rt,int pre){
//cout<<rt<<" "<<pre<<endl;
fa[rt][0]=pre;
dep[rt] = dep[pre]+1;
for(int i=1;i<=20;++i)
fa[rt][i] = fa[fa[rt][i-1]][i-1];
for(int i=head[rt];i!=-1;i=ed[i].nxt){
int v = ed[i].to;
if(v==pre) continue;
dfs(v,rt);
}
}
inline int find_LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
int d = dep[x]-dep[y];
for(int i=0;(1<<i)<=d;++i)
if((1<<i)&d) {
x = fa[x][i];
d -= (1<<i);
}
if(x==y) return x;
//cout<<x<<" "<<y<<endl;
for(int i=20;i>=0;--i){
if(fa[x][i]==fa[y][i]) continue;
x =fa[x][i];
y =fa[y][i];
//cout<<x<<" "<<y<<endl;
}
return fa[x][0];
}
int main(){
while(~scanf("%d",&n)){
toUsed = 1;
memset(head,-1,sizeof head);
memset(fa,0,sizeof fa);
memset(lca,0,sizeof lca);
for(int i=1;i<n;++i){
int u,v;
//scanf("%d%d",&u,&v);
u = read(); v = read();
cout <<u<<" "<<v<<endl;
cout<<endl;
add_edge(u,v);
}
dep[0]=0;
dfs(1,0);
/*cout<<fa[3][0]<<" "<<fa[11][0]<<endl;*/
for(int i=1;i<=n;++i) lca[i][0] = i;
for(int j=1;(1<<j)<=n;++j)
for(int i=1;i+(1<<j)-1<=n;++i)
lca[i][j] = find_LCA(lca[i][j-1],lca[i+(1<<(j-1))][j-1]);
int q;
// scanf("%d",&q);
q = read();
while(q--){
int l,r;
//scanf("%d%d",&l,&r);
l = read(); r = read();
int d = (int)( log((double)(r-l+1)) / log(2.0) );
//cout<<d<<endl;
//cout<<lca[l][d]<<" "<<lca[r-(1<<d)+1][d]<<endl;
printf("%d\n",find_LCA(lca[l][d],lca[r-(1<<d)+1][d]));
}
}
return 0;
}
/*********
13
1 2
1 10
10 11
2 6
5 6
4 6
3 4
2 12
12 13
2 7
7 8
7 9
10
**************/
三、贪心+树上倍增
树上节点i的权值为2^i, 删去k个节点后仍然为树,问留下的树的节点权值和最大值
CF 980E
贪心做法,显然2^n >其它n-1个节点权值之和,所以建立以节点n为根的树,然后逐一判断n-1、n-2……1,如果它到达已经在确定留在树上的节点的距离大于x(剩下留在树上的节点数),我们必须舍去!否则将其路径上的点全变为确定留在树上!
/*******************************************************************
** 给你一棵树,树上节点的权值为其idx 的2^idx,问删去k个节点,仍为树,
问留下哪n-k个节点的权值之和最大。
** 明显: 第n节点肯定保留,其余剩下所有节点的和小于其权值;
建立以节点n为根的树!
找其它n-k-1个点,就有点难了! 找此时最大的节点,必须确定它的未确定保留的最远祖先的位置(层数)
如果之间长度为x 远大于现在能保留的个数,我们就必须舍弃此点。
查找的话如果我们简单查询距离,那么准备超时了!
倍增查询!
********************************************************************/
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <set>
#include <queue>
#include <map>
#include <vector>
#include <stack>
#define llt long long
#define Ld long double
#define lson (rt<<1)
#define rson ((rt<<1)+1)
using namespace std;
typedef pair<int,int> pii;
const int Size = 1e6+7;
int fa[Size][22],dep[Size],q[Size];//q 记录 被保留
vector <int> V[Size];
vector <int> Ans;
void init(){
memset(fa,0,sizeof(fa));
memset(q,0,sizeof(q));
memset(dep,0,sizeof(dep));
memset(V,0,sizeof(V));
Ans.clear();
}
// create tree = dfs
// create redouble relation
void mk_Tree(int rt){
for(int i=1;i<=20;++i)
fa[rt][i] = fa[fa[rt][i-1]][i-1];
int num = V[rt].size();
for(int i=0;i<num;++i){
int sn = V[rt][i];
//cout<<sn<<endl;
if(fa[rt][0]==sn) continue;
fa[sn][0] = rt;
dep[sn] = dep[rt] + 1;
mk_Tree(sn);
}
}
int main()
{
int n,k;
scanf("%d %d",&n,&k);
int a,b;
init();
for(int i=1;i<n;++i){
scanf("%d %d",&a,&b);
V[a].push_back(b);
V[b].push_back(a);
}
fa[n][0] = 0;//can't -1
q[n] = 1;
Ans.push_back(n);
mk_Tree(n);
int r = n - k - 1;//记录需要保留的点个数
for(int i=n-1;i>=1&&r;--i){
if(q[i]) continue;
// 倍增查找
int tmp = i;
for(int j=log2(1.0*n);j>=0;--j){
if(fa[tmp][j]==0) continue;
int sn = fa[tmp][j];
if(q[sn]==0) tmp = sn;//前进2^j个单位
}
if(dep[i]-dep[tmp]+1>r) continue;
q[i] = 1;
r-=(dep[i]-dep[tmp]+1);
int t=i;
while(t!=tmp){
t = fa[t][0];
q[t] = 1;
}
}
for(int i=1;i<=n;++i)
if(q[i]==0&&k--) printf("%d%c",i,(k==0)?'\n':' ');
return 0;
}