P5311 Ynoi2011 成都七中

彬彬冰激凌發表於2024-04-13

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]);
}

相關文章