1. 程式人生 > 實用技巧 >洛谷P4556 雨天的尾巴

洛谷P4556 雨天的尾巴

題目連結: P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合併
題目大意:
有一顆 \(n\) 個節點的樹,\(m\) 次操作,每次將節點 \(u\)\(v\) 的路徑上的每個點放一個物品 \(c\) ,最後詢問每個節點上數量最多的物品是什麼,其中數量相同的物品取編號最小者,若無物品輸出0。
\(n,m,c\leq 10^5\)
思路:
如題目名稱,模板題,蒟蒻之前沒寫過線段樹合併,就簡單總結一下吧。
線段樹合併有兩種寫法:

  1. \(b\) 直接合併到 \(a\) 上去:
int merge(int a,int b){
    if(!a)return b;
    if(!b)return a;
    if(t[a].l==t[a].r){
        //對線段樹儲存資訊的葉節點進行具體的合併
    }else{
        t[a].ls=merge(t[a].ls,t[b].ls);
        t[a].rs=merge(t[a].rs,t[b].rs);
        update(a);
    }
    return a;
}

這種寫法會使 \(a\) 直接繼承了部分 \(b\) 的節點,所以之後對 \(a\) 進行其他的修改時,\(b\) 也會被改掉,因此這麼寫只適用與離線查詢(如此題)。

  1. 新建節點儲存合併後的樹
int merge(int a,int b){
    if(!a)return b;
    if(!b)return a;
    int root=++cnt;
    if(t[a].l==t[a].r){
        t[root]=....;//進行具體的資訊合併
    }else{
        t[root].ls=merge(t[a].ls,t[b].ls);
        t[root].rs=merge(t[a].rs,t[b].rs);
        update(root);
    }
    return root;
}

這樣寫就可以適用於動態查詢,其缺點是空間複雜度比較大,在離線查詢時線段樹的空間約為前者的兩倍。

對於這道題,我們考慮樹上差分,對於每一個節點建一顆動態開點權值線段樹,然後dfs時節點和自己的所有兒子合併即可。

細節:

  • 如果寫的是第一種合併方式,那麼每個節點需要在回溯前將答案儲存到 \(ans_i\) 中,因為之後該節點上的線段樹可能會被其祖先修改。
  • 嘗試將 \(Tarjan\)\(LCA\)\(dfs\) 寫到一起是一件很荒謬的事情,計算答案的先後順序會出大問題。
  • 日常:當 \(LCA\)\(a\)\(b\) 是同一個節點時,只記錄一次。

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<fstream>
#define N 100100
#define R 100000
using namespace std;
inline int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*w;
}
struct tree{
    int l,r;
    int mx,node;
    int ls,rs;
}t[17*4*N];
int head[N],to[N*2],nxt[N*2];
int cnt_n,cnt_e;
int root[N];
int ancest[N],fa[N];
int ans[N];
vector<pair<int,int> > upd[N];
bool vis[N];
void init(int n){
    cnt_e=-1;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)fa[i]=i;
}
void add_e(int a,int b,bool id){
    nxt[++cnt_e]=head[a];
    head[a]=cnt_e;
    to[cnt_e]=b;
    if(id)add_e(b,a,0);
}
void update(int x){
    int a=t[t[x].ls].mx,b=t[t[x].rs].mx;
    int c=t[t[x].ls].node,d=t[t[x].rs].node;
    t[x].mx=max(a,b);
    if(t[x].rs==0||a>b||(a==b&&c<d))t[x].node=c;
    else t[x].node=d;
}
void insert(int &a,int l,int r,int pos,int k){
    if(!a)a=++cnt_n,t[a].l=l,t[a].r=r;
    if(l==r){
        t[a].mx+=k,t[a].node=l;return;
    }
    int mid=(l+r)>>1;
    if(mid>=pos)insert(t[a].ls,l,mid,pos,k);
    else insert(t[a].rs,mid+1,r,pos,k);
    update(a);
}
int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
int merge(int a,int b){
    if(!a)return b;
    if(!b)return a;
    int l=t[a].l,r=t[a].r;
    if(l==r){
        t[a].mx+=t[b].mx;
    }else{
        t[a].ls=merge(t[a].ls,t[b].ls);
        t[a].rs=merge(t[a].rs,t[b].rs);
        update(a);
    }
    return a;
}
void tarjan(int x,int fath){
    ancest[x]=fath;
    for(int i=head[x];~i;i=nxt[i]){
        if(vis[to[i]]||to[i]==fath)continue;
        tarjan(to[i],x);
    }
    vis[x]=true;
    for(int i=upd[x].size()-1;i>=0;i--){
        int y=upd[x][i].first,z=upd[x][i].second;
        if(vis[y]){
            insert(root[y],1,R,z,1);
            insert(root[x],1,R,z,1);
            int lca=find(y);
            insert(root[lca],1,R,z,-1);
            insert(root[ancest[lca]],1,R,z,-1);
        }
    }
    fa[x]=fath;
}
void dfs(int x){
    for(int i=head[x];~i;i=nxt[i]){
        if(to[i]==ancest[x])continue;
        dfs(to[i]);
        root[x]=merge(root[x],root[to[i]]);
    }
    ans[x]=t[root[x]].mx>0?t[root[x]].node:0;
}
int main(){
    int n,m;
    int x,y,z;
    cin>>n>>m;
    init(n);
    for(int i=1;i<n;i++){
        add_e(read(),read(),1);
    }
    for(int i=0;i<m;i++){
        x=read(),y=read(),z=read();
        upd[x].push_back(make_pair(y,z));
        if(x!=y)upd[y].push_back(make_pair(x,z));
    }
    tarjan(1,0);
    dfs(1);
    for(int i=1;i<=n;i++)
        printf("%d\n",ans[i]);
    return 0;
}