[NOIP2016] 天天愛跑步
前言
看這道題不爽很久了,但一直沒有開它,原因是我不會(我太菜了),看了題解還是寫不來,因為我不會線段樹合併。
然後今天學了dsu on tree這種神奇的科技,成功把它A了,效率吊打線段樹合併。
於是寫篇題解紀念一下。
題目連結
演算法概述
不帶修改的樹上路徑資訊的維護,很容易想到樹上差分。
我們考慮一條路徑,會對哪些點產生貢獻,也就是題目描述中的一個人會被哪些觀察員觀察到。
假設這條路徑為 \((s,t)\),點為 \(x\),我們分兩部分考慮:
-
若點 \(x\) 在 \(s\) 到 \(lca(s,t)\)(含)的路徑上,則若滿足 \(w[x]=dep[s]-dep[x]\)
將式子變形:\(w[x]+dep[x]=dep[s]\),我們就可以發現一個事情,對於任何一個點 \(p\),這種情況的點數即為所有路徑中起點深度為 \(w[p]+dep[p]\) 的數量。
我們可以形象化地理解為,有 \(m\) 個人,每個人會給 \(s\) 到 \(lca(s,t)\) 路徑上的每個點發放一個型別為 \(dep[s]\) 的物品,而我們要求的就是對於每個點 \(x\),其上型別為 \(w[x]+dep[x]\) 的物品有多少個。
再抽象一點,即一條路徑 \(s→lca(s,t)\),會將路徑上每個點處權值為 \(dep[s]\)
此時,樹上差分已經呼之欲出了。我們在每個點上開一個桶,對於每條路徑 \((s,t)\),在點 \(s\) 讓下標為 \(dep[s]\) 的值\(+1\),在點 \(fa[lca(s,t)]\) 上讓下標為 \(dep[s]\) 的值 \(-1\),其中 \(fa[i]\) 表示 \(i\) 的父親節點,最後每個點的資訊應該是以其為根的子樹和,這部分答案即為該點的桶內下標為 \(w[x]+dep[x]\) 的值。
-
若點 \(x\) 在 \(lca(s,t)\)(不含)的路徑上,設路徑長度為 \(dis\)
那麼同樣應用樹上差分,在點 \(t\) 上使下標為 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(+1\),在 \(lca(s,t)\) 上使下標為 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(-1\),最後每個點的資訊統計子樹和,答案即為點上下標為 \(w[x]-dep[x]\) 的值。
我們設第一種情況開的桶為 \(up\),第二種情況開的桶為 \(down\),那麼對於每個點 \(x\),其最終答案即為 \(up[w[x]+dep[x]]+down[w[x]-dep[x]]\)。
但問題是,我們在每個點上開兩個桶是顯然不可能的,而且就算空間能夠支援每個點開兩個桶,但我們在統計過程中,合併兩個桶的時間複雜度也直接爆炸。
所以我們只能在全域性開兩個桶。
那麼怎麼維護子樹資訊呢?
不帶修改的子樹資訊維護,正是dsu on tree大顯身手的領域。
所以在每個點上開個 \(vector\),把每個點要維護的資訊放進去,然後dsu on tree,需要統計資訊的時候再把資訊拿出來統計就好了。
每個點維護資訊的時間均攤是 \(\frac{4m}{n}\) 的,每個點最多會被統計 \(O(logn)\) 次,一共有 \(n\) 個點,所以最後的時間複雜度是 \(O(mlogn)\) 的。
參考程式碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=3e5+10;
struct Edge{
int to,nex;
}edge[N<<1];int idx;
int h[N];
void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
int fa[N],dep[N],siz[N];
int son[N],top[N],w[N];
vector<int> info[N][4];
int up[N<<1],BUF[N<<2],*down=&BUF[N<<1];
int ans[N];
int n,m;
void dfs1(int p,int f)
{
fa[p]=f;
dep[p]=dep[f]+1;
siz[p]=1;
int max_son=0;
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==f)continue;
dfs1(to,p);
if(siz[to]>max_son)max_son=siz[to],son[p]=to;
siz[p]+=siz[to];
}
}
void dfs2(int p,int t)
{
top[p]=t;
if(!son[p])return;
dfs2(son[p],t);
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==son[p])continue;
dfs2(to,to);
}
}
inline int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void calc(int p,int flag,int val)
{
for(int i=0;i<info[p][0].size();i++)up[info[p][0][i]]+=val;
for(int i=0;i<info[p][1].size();i++)up[info[p][1][i]]-=val;
for(int i=0;i<info[p][2].size();i++)down[info[p][2][i]]+=val;
for(int i=0;i<info[p][3].size();i++)down[info[p][3][i]]-=val;
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==flag)continue;
calc(to,flag,val);
}
}
void dfs(int p,int keep)
{
for(int i=h[p];~i;i=edge[i].nex)
{
int to=edge[i].to;
if(to==fa[p]||to==son[p])continue;
dfs(to,0);
}
if(son[p])dfs(son[p],1);
calc(p,son[p],1);
ans[p]=up[dep[p]+w[p]]+down[w[p]-dep[p]];
if(!keep)calc(p,0,-1);
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=1,a,b;i<=n-1;i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
dfs1(1,0);
dfs2(1,1);
for(int i=1,s,t;i<=m;i++)
{
scanf("%d%d",&s,&t);
int z=lca(s,t);
info[s][0].push_back(dep[s]);
info[fa[z]][1].push_back(dep[s]);
info[t][2].push_back(dep[s]-2*dep[z]);
info[z][3].push_back(dep[s]-2*dep[z]);
}
dfs(1,1);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}