P5311 Ynoi2011 成都七中

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]);
}
posted @ 2024-04-13 16:31  彬彬冰激凌  阅读(6)  评论(0编辑  收藏  举报