洛谷P4556 雨天的尾巴
阿新 • • 發佈:2021-01-09
題目連結: P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合併
題目大意:
有一顆 \(n\) 個節點的樹,\(m\) 次操作,每次將節點 \(u\) 到 \(v\) 的路徑上的每個點放一個物品 \(c\) ,最後詢問每個節點上數量最多的物品是什麼,其中數量相同的物品取編號最小者,若無物品輸出0。
\(n,m,c\leq 10^5\)
思路:
如題目名稱,模板題,蒟蒻之前沒寫過線段樹合併,就簡單總結一下吧。
線段樹合併有兩種寫法:
- 將 \(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\) 也會被改掉,因此這麼寫只適用與離線查詢(如此題)。
- 新建節點儲存合併後的樹
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; }