树上倍增的应用

树上倍增的应用

归根到底,树上倍增的应用就是求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][2k1],lca[l+2k1][2k1]) 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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值