解題報告:luogu P4556雨天的尾巴 (樹上對點差分 + 動態開點 + 線段樹合併)線段樹合併模板離線/在線詳解

題目鏈接:雨天的尾巴

在這裏插入圖片描述

本題本身是一個非常簡單的一道樹上差分的模板題,但是由於變態的數據範圍,我們直接用數組是存不下的,所以就要用到動態開點的線段樹來存,並且支持修改某個位置,維護區間最大值,以及保存最大值所在位置的功能。而得到差分數組以後我們需要還原成原數組,這裏由於每一顆線段樹都是一個差分數組的一個數,所以原本的循環差分數組求和就用線段樹合併來代替。

這裏線段樹們是代替原來的差分數組(因爲數據範圍太大空間開不下)

線段樹合併一共有兩種方法

1.離線算法

int merge(int a,int b,int x,int y) {
	if(!a) return b;
	if(!b) return a;
	if(x==y) {d[a]+=d[b];t[a]=x;return a;}
	int mid=x+y>>1;
	l[a]=merge(l[a],l[b],x,mid),
	r[a]=merge(r[a],r[b],mid+1,y);
	pushup(a);
	return a;
}

把b合併到a上
但是我們這樣直接把b合併過來的話,在以後繼續合併a的時候可能合併過程中就會破壞b的結構,所以這種方法只適合於離線,合併完成之後立刻詢問我們也可以像主席樹那樣,合併不在原來的樹上而是新開節點,這樣就不需要離線了,一邊詢問一邊用

2.在線算法

int merge(int a,int b,int x,int y) {
	if(!a) return b;
	if(!b) return a;
	int root=++cnt;
	if(x==y) {d[root]=d[a]+d[b];t[root]=x;return root;}
	int mid=x+y>>1;
	l[root]=merge(l[a],l[b],x,mid),
	r[root]=merge(r[a],r[b],mid+1,y);
	pushup(root);
	return root;
}

缺點是比較消耗空間

所以這道題,我們直接對每一個節點維護一棵權值線段樹,之後我們把一個詢問變成一次樹上差分的形式,之後直接向上合併線段樹即可

總結:

  • 線段樹的動態開點要設置一個根數組並且需要預處理使得root[i]=iroot[i] = i自己是自己的根
  • TLE不一定都是因爲死循環了,(死循環很可能是因爲遞歸函數沒有設置結束條件,遍歷圖的時候沒有判斷迴環,以及for循環裏sb的鍋)TLE還有可能是數組開小了(線段樹的數組,不是RE,而是TLE)
  • 普通的單一線段樹要開4倍空間 , 線段樹合併的時候線段樹要開50倍 ! 不然就會TLE !

這裏講一下本題中的線段樹爲什麼至少要開50倍空間。
我們會發現一共n = 1e5個數據,每次進行至少三次insert操作,每次insert操作的空間複雜度是O(logn)O(logn),總的空間複雜度爲O(nlogn3)O(nlogn*3) =log2(1e5)31e5= log_2(1e5)*3*1e5 =1731e5=5e6= 17*3*1e5 = 5e6,所以普通的數組開N=1e5N = 1e5,前向星數組開2N2N,線段樹數組開50N50N

//樹上對點差分 + 線段樹動態開點 + 線段樹合併
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>

//#define ls (p<<1)
//#define rs (p<<1|1)

#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 1e5+7;
const int M = 2007;
struct Seg{
    int lc;
    int rc;
    int dat;//題目要求輸出最多的類型,所以線段樹維護的是最大值
    int pos;//有物品就是記錄位置l,沒有物品就是0因爲最後輸出的也是0
}tree[N * 20 * 4];

//離線操作
int fa[N][20],deep[N];//lca專用
int root[N],ans[N];
int ver[N<<1],nex[N<<1],head[N];//前向星專用
int X[N],Y[N],Z[N],val[N];//存點和點的離散化
int T,n,m,tot,t,num,cnt;
queue<int>q;//bfs專用

void add(int u,int v){
    ver[++tot] = v;
    nex[tot] = head[u];
    head[u] = tot;
}

void bfs(){//lca預處理
    q.push(1);
    deep[1] = 1;//根節點的深度爲1
    while(q.size()){
        int u = q.front();
        q.pop();
        for(int i = head[u];i;i = nex[i]){
            int v = ver[i];
            if(deep[v])continue;
            deep[v] = deep[u] + 1;
            fa[v][0] = u;
            for(int j = 1 ;j <= t;++j)
                fa[v][j] = fa[fa[v][j-1]][j-1];
            q.push(v);
        }
    }
}

int lca(int x,int y){
    if(deep[x] > deep[y])swap(x,y);
    for(int i = t ;i >= 0;i--)
        if(deep[fa[y][i]] >= deep[x])
            y = fa[y][i];
    if(x == y)return x;
    for(int i = t ;i >= 0;i--)
        if(fa[x][i] != fa[y][i])
            x = fa[x][i],y = fa[y][i];
    return fa[x][0];
}

//動態開點省空間,所以不需要建樹
void Insert(int p,int l,int r,int val,int delta){
    if(l == r){
        tree[p].dat += delta;
        tree[p].pos = tree[p].dat ? l : 0;
        return ;
    }
    int mid = (l + r) >> 1;
    if(val <= mid){
        if(!tree[p].lc)tree[p].lc = ++num;
        Insert(tree[p].lc,l,mid,val,delta);
    }
    else {
        if(!tree[p].rc)tree[p].rc = ++num;
        Insert(tree[p].rc,mid+1,r,val,delta);
    }
    tree[p].dat = max(tree[tree[p].lc].dat,tree[tree[p].rc].dat);
    tree[p].pos = tree[tree[p].lc].dat >= tree[tree[p].rc].dat ? tree[tree[p].lc].pos : tree[tree[p].rc].pos;
}
//每一棵線段樹都是一個差分數組的點
int Merge(int p,int q,int l,int r){
    if(!p)return q;
    if(!q)return p;
    if(l == r){
        tree[p].dat += tree[q].dat;
        tree[p].pos = tree[p].dat ? l : 0;
        return p;
    }
    int mid = (l + r) >> 1;
    tree[p].lc = Merge(tree[p].lc,tree[q].lc,l,mid);
    tree[p].rc = Merge(tree[p].rc,tree[q].rc,mid+1,r);
    tree[p].dat = max(tree[tree[p].lc].dat,tree[tree[p].rc].dat);
    tree[p].pos = tree[tree[p].lc].dat >= tree[tree[p].rc].dat ? tree[tree[p].lc].pos : tree[tree[p].rc].pos;
    return p;//上一行的>=解決了題中如果相等就輸出編號小的那一個的要求
}

void dfs(int u){
    for(int i = head[u];i;i = nex[i]){
        int v = ver[i];
        if(deep[v] <= deep[u])//如果回來的就continue,本來應該是vis但是有預處理了deep也可以用deep
            continue;
        dfs(v);
        root[u] = Merge(root[u],root[v],1,cnt);
    }
    ans[u] = tree[root[u]].pos;//存的是離散化後的下標
}

int main()
{
    scanf("%d%d",&n,&m);
    t = (int)(log(n) / log(2)) + 1;
    over(i,1,n-1){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    bfs();
    over(i,1,n)root[i] = ++num;//根節點是它自己,但是這裏是動態開點的編號
    over(i,1,m){
        scanf("%d%d%d",&X[i],&Y[i],&Z[i]);
        val[i] = Z[i];//離散化
    }
    sort(val + 1,val + m + 1);
    cnt = unique(val+1,val+1+m) - (val+1);
    over(i,1,m){
        int x = X[i],y = Y[i];
        int z = lower_bound(val+1,val+1+cnt,Z[i]) - val;
        int p = lca(x,y);
        Insert(root[x],1,cnt,z,1);//維護差分數組
        Insert(root[y],1,cnt,z,1);
        Insert(root[p],1,cnt,z,-1);//對點差分
        if(fa[p][0])Insert(root[fa[p][0]],1,cnt,z,-1);
    }
    dfs(1);
    over(i,1,n)
        printf("%d\n",val[ans[i]]);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章