线段树合并总结

线段树合并总结

咕咕咕中

原理

复杂度

两棵线段树合并复杂度即为两棵树公共节点数\(\times logn\),据说因为实际重合部分一般比较少,一次合并两棵线段树的复杂度近似为\(O(logn)\),所以合并\(n\)棵线段树复杂的为\(O(nlog_n)\)

据说空间复杂度也是\(nlogn\)

CF600E Lomsat gelral

一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。

权值线段树,下标为颜色,维护区间答案和最多颜色编号和,然后自底向上合并即可。

#include <cstdio>
#define MAXN 100010
#define sl tre[x].l
#define sr tre[x].r
#define ll long long
using namespace std;
int head[MAXN],vv[MAXN*2],nxt[MAXN*2],tot;
inline void add_edge(int u, int v){
    vv[++tot]=v;
    nxt[tot]=head[u];
    head[u]=tot;
}
struct nod{
    int l, r;
    ll ans, sum;
}tre[MAXN*2*20];
int rt[MAXN],cnt,n;
int col[MAXN];
ll res[MAXN];
void push_up(int x){
    if(tre[sl].sum>tre[sr].sum){
        tre[x].sum=tre[sl].sum;
        tre[x].ans=tre[sl].ans;
    }else if(tre[sl].sum<tre[sr].sum){
        tre[x].sum=tre[sr].sum;
        tre[x].ans=tre[sr].ans;
    }else{
        tre[x].sum=tre[sl].sum;
        tre[x].ans=tre[sl].ans+tre[sr].ans;
    }
}
int merge(int a, int b, int l, int r){
    if(a==0) return b;
    if(b==0) return a;
    if(l==r){
        tre[a].ans=l;
        tre[a].sum+=tre[b].sum;
        return a;
    }
    int mid=(l+r)>>1;
    tre[a].l=merge(tre[a].l, tre[b].l, l, mid);
    tre[a].r=merge(tre[a].r, tre[b].r, mid+1, r);
    push_up(a);
    return a;
}
void update(int &cur, int l, int r, int pos, int val){
    if(cur==0) cur=++cnt;
    if(l==r){
        tre[cur].sum+=(ll)val;
        tre[cur].ans=l;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) update(tre[cur].l, l, mid, pos, val);
    else update(tre[cur].r, mid+1, r, pos, val);
    push_up(cur);
}
void dfs(int u, int f){
    for(int i=head[u];i;i=nxt[i]){
        int v=vv[i];
        if(v==f) continue;
        dfs(v, u);
        merge(rt[u], rt[v], 1, 100000);
    }
    update(rt[u], 1, 100000, col[u], 1);
    res[u]=tre[rt[u]].ans;
}
int main(){
    scanf("%d", &n);
    cnt=n;
    for(int i=1;i<=n;++i)
        scanf("%d", &col[i]),rt[i]=i;
    for(int i=1;i<=n-1;++i){
        int u,v;
        scanf("%d %d", &u, &v);
        add_edge(u, v);
        add_edge(v, u);
    }
    dfs(1, 0);
    for(int i=1;i<=n;++i) printf("%lld ", res[i]);
    return 0;
}

[湖南集训]谈笑风生

给一棵树\(n\)个节点,\(q\)次询问,每次给定\(p,k\),问有多少三元组\((p,b,c)\)满足\(p,b\)均为\(c\)的父亲,\(p,b\)在树上的距离不超过\(k\)
\(n,q\le 10^5\)

两种情况:

  • \(b\)\(a\)的上面,我们发现\(b\)\(min(dep[a]-1, k)\)个可选位置,而\(a\)的子树内所有节点(除了节点\(a\)本身)均可作为\(c\),所以共有$min(dep[a]-1, k)\times(sz[a]-1) $个三元组满足。
  • 对于\(b\)\(a\)的下面,我们可以每个节点都维护一颗下标为深度的权值线段树,因为节点\(u\)作为\(b\)的同时,其子树除\(u\)外均可作为\(c\),所以对于深度\(dep[u]\)的贡献为\(sz[u]-1\),然后在\(dfs\)回溯时同时合并子树线段树,每次答案即为深度区间\([dep[a]+1, dep[a]+k]\)的区间和。
#include <cstdio>
#include <algorithm>
#define MAXN 300003
#define MAXM 300003*30
#define ll long long
using namespace std;
int head[MAXN],nxt[MAXN*2],vv[MAXN*2],tot;
inline void add_edge(int u, int v){
    vv[++tot]=v;
    nxt[tot]=head[u];
    head[u]=tot;
}
int cnt;
ll tre[MAXM*2];
int sl[MAXM*2],sr[MAXM*2];
void change(int &x, int l, int r, int pos, int val){
    if(x==0) x=++cnt;
    tre[x]+=val;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(pos<=mid) change(sl[x], l, mid, pos, val);
    else change(sr[x], mid+1, r, pos, val);
}
ll query(int x, int l, int r, int ql, int qr){
    if(x==0) return 0;
    if(ql<=l&&r<=qr) return tre[x];
    int mid=(l+r)>>1;
    ll res=0;
    if(ql<=mid) res+=query(sl[x], l, mid, ql, qr);
    if(mid<qr) res+=query(sr[x], mid+1, r, ql, qr);
    return res;
}
int merge(int a, int b, int l, int r){
    if(a==0||b==0) return a+b;
    int mid=(l+r)>>1;
    int x=++cnt;
    tre[x]=tre[a]+tre[b];
    sl[x]=merge(sl[a], sl[b], l, mid);
    sr[x]=merge(sr[a], sr[b], mid+1, r);
    return x;
}
int n,q;
int sz[MAXN],rot[MAXN],dep[MAXN];
void dfs(int u, int fa){
    sz[u]=1;
    dep[u]=dep[fa]+1;
    for(int i=head[u];i;i=nxt[i]){
        int v=vv[i];
        if(v==fa) continue;
        dfs(v, u);
        sz[u]+=sz[v];
    }
    change(rot[u], 1, n, dep[u], sz[u]-1);
    rot[fa]=merge(rot[fa], rot[u], 1, n);
}
int main(){
    scanf("%d %d", &n, &q);
    for(int i=1;i<n;++i){
        int a,b;scanf("%d %d", &a, &b);
        add_edge(a, b);add_edge(b, a);
    }
    dfs(1, 0);
    while(q--){
        int p,k;scanf("%d %d", &p, &k);
        printf("%lld\n", query(rot[p], 1, n, dep[p]+1, dep[p]+k)+(ll)(sz[p]-1)*min(k, dep[p]-1));
    }
    return 0;
}

天天爱跑步

Peaks 线段树合并

[HNOI2012]永无乡 线段树合并

P4556 雨天的尾巴 线段树合并

posted @ 2019-09-28 15:40  Santiego  阅读(263)  评论(0编辑  收藏  举报