P5311 Ynoi2011 成都七中
點分樹好題,太妙了。
思路
看到樹和連通塊,考慮點分樹。
但是從這裡發現原樹和點分樹的聯絡實在太小,貌似不可做。
可以發現對於一個詢問,一個點如果和 \(x\) 在一個連通塊內,那麼這個點到 \(x\) 的最大最小節點編號肯定都在 \([l,r]\) 這個範圍內。
可以證明,對於一個連通塊,肯定存在一個在 \(x\) 的連通塊內的節點 \(u\),節點 \(u\) 在點分樹上最淺,且節點 \(u\) 在點分樹上的子樹包含了整個連通塊。
證明如下:
證明一:在原樹中刪除點分樹上的一個點,會把原樹分為若干個連通塊,在構建點分樹的過程中 \(x\) 的連通塊內,最先被選中作為點分樹上的一個節點為 \(u\) ,根據構建規則,剩下的點會成為 \(u\) 的子樹內的節點,滿足了 \(u\) 最淺和全覆蓋。
證明二:如果點分樹上一個節點 \(v\) 不是在連通塊內,那麼 \(x\) 連通塊內的所有節點肯定都在該節點的同一個兒子的子樹中。因為如果在不同兒子的子樹中,這樣節點 \(v\) 肯定也會在 \(x\) 的連通塊中,與題設不符。順著這個兒子走,直到走到一個在連通塊內的點即可。
想要找到點 \(u\) 只需要依次遍歷點分樹上 \(x\) 最淺的祖先,選擇滿足原樹上到 \(x\) 的最大最小節點編號在 \([l,r]\) 內的最淺的祖先。我們將這個選擇的點定義為關鍵點。
每一個詢問都對應了一個關鍵點,我們只需要查詢點分樹上關鍵點所在子樹內,原樹上到關鍵點最大最小節點編號在 \([l,r]\) 內的節點即可。(原樹上關鍵點到 \(x\) 最大最小節點編號在 \([l,r]\) 內,可以從子樹內的這個點到關鍵點,再到 \(x\) ,原樹上最大最小編號也肯定在 \([l,r]\) 內)
如果每個詢問做一次,效率甚至不如暴力,但我們可以把多個詢問合在同一個點上做。
對於一個點分樹上的點 \(u\),我們把其子樹內的點到 \(u\) 的原樹上最大最小編號計為 \((mn,mx)\),關鍵點為 \(u\) 詢問存為 \((l,r)\)。
把詢問和點混在一起按照第一維降序排序,然後每一種顏色記錄最小的 \(mx\),遇到一個查詢直接查詢有多少顏色的 \(mx\) 小於 \(r\) 即可,這裡可以用樹狀陣列維護。
這裡先加入樹狀陣列的點滿足了 \(l\leq mn\),查詢時又滿足了 \(mx\leq r\)。
時間複雜度:
樹分治的子樹內節點排序:\(O(n\log^2 n)\)。
樹分治加樹狀陣列:\(O(n\log^2 n)\)。
總時間複雜度:\(n\log^2 n\)。
CODE
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct Edge
{
int tot;
int head[maxn];
struct edgenode{int to,nxt;}edge[maxn*2];
inline void add(int x,int y)
{
tot++;
edge[tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
}T;
struct node{int x,y,c,tp;};
struct treearray
{
int tree[maxn];
inline int lowbit(int x){return x&(-x);}
inline void updata(int x,int y){for(;x<=maxn-5;x+=lowbit(x)) tree[x]+=y;}
inline int getsum(int x){int sum=0;for(;x;x-=lowbit(x)) sum+=tree[x];return sum;}
inline void clr(int x){for(;x<=maxn-5;x+=lowbit(x)) tree[x]=0;}
}S;
struct fanode{int x,y,f;};//加入祖先時,記錄到祖先的最大最小節點編號
inline bool cmp(node a,node b){return a.x>b.x||(a.x==b.x&&a.tp<b.tp);}
int n,m,rt;
int c[maxn],siz[maxn],mv[maxn],ans[maxn];
int mx[maxn],mn[maxn];
vector<int>s[maxn];
vector<fanode>fa[maxn];
vector<node>t[maxn];
bool book[maxn],cut[maxn];
inline int dfs_siz(int u)
{
book[u]=true;siz[u]=1;
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(!book[v]&&!cut[v]) siz[u]+=dfs_siz(v);
}
book[u]=false;return siz[u];
}
inline int dfs_rt(int u,const int tot)
{
book[u]=true;int ret=u;
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(!book[v]&&!cut[v]&&siz[v]*2>=tot){ret=dfs_rt(v,tot);break;}
}
book[u]=false;return ret;
}
inline void dfs2(int u,int g,int mn,int mx)
{
mx=max(mx,u),mn=min(mn,u);
t[g].push_back({mn,mx,c[u],0});//加入節點
book[u]=true;fa[u].push_back({mn,mx,g});
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(!book[v]&&!cut[v]) dfs2(v,g,mn,mx);
}
book[u]=false;siz[u]=1;
}
inline void solve(int u,int f)//建樹
{
dfs_siz(u);int g=dfs_rt(u,siz[u]);cut[g]=true;
s[f].push_back(g),fa[g].push_back({g,g,g});
t[g].push_back({g,g,c[g],0});
for(int i=T.head[g];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(!cut[v]){dfs2(v,g,g,g);solve(v,g);}
}
rt=g;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
T.add(x,y),T.add(y,x);
}
solve(1,0);
for(int i=1;i<=m;i++)
{
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
for(auto j:fa[x])
if(l<=j.x&&j.y<=r){t[j.f].push_back({l,r,i,1});break;}
}
memset(mv,0x7f,sizeof(mv));
for(int i=1;i<=n;i++)
{
sort(t[i].begin(),t[i].end(),cmp);
for(auto j:t[i])
if(j.tp) ans[j.c]=S.getsum(j.y);
else if(j.y<mv[j.c]){if(mv[j.c]<1e9)S.updata(mv[j.c],-1);S.updata(mv[j.c]=j.y,1);}
for(auto j:t[i]) if(!j.tp) S.clr(mv[j.c]),mv[j.c]=1e9;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}