peiwenjun's blog 没有知识的荒原

P6782 [Ynoi2008] rplexq 题解

题目描述

给定 \(n\) 个节点的有根树,根节点为 \(rt\)

\(m\) 次询问,给定 \(l,r,x\) ,求 \(\sum\limits_{l\le i\lt j\le r}[\texttt{lca}(i,j)=x]\)

数据范围

  • \(1\le n,m\le 2\cdot 10^5\)
  • \(1\le l\le r\le n,1\le rt,x\le n\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{128MB}\)

分析

这个难度没道理不评黑啊。

\(f(x,l,r)\) 为编号在 \([l,r]\) 之间且在 \(x\) 子树的点数,则:

\[\sum_{l\le i\lt j\le r}[\texttt{lca}(i,j)=x]=\frac 12\bigg(f^2(x,l,r)-\sum f^2(y_i,l,r)-[l\le x\le r]\bigg)\\ \]

其中 \(y_i\)\(x\) 的所有子节点。

\(x\) 子树的限制看成 \(dfn\) 的区间限制,因此 \(f\) 的本质是二维数点。

\(x\) 的儿子个数根号分治:

  • \(x\) 的儿子个数 \(\le B\) ,将单个询问拆成 \(\le B+1\) 个关于 \(f\) 的询问。这样总共有 \(n\) 个点和 \(\mathcal O(mB)\) 次二维数点,套用 \(\mathcal O(\sqrt n)-\mathcal O(1)\) 分块即可。

    但是这样会遇到新的问题:我们开不了 \(\mathcal O(mB)\) 的空间,询问根本存不下来。

    接下来的卡空间操作是本题的神来之笔:将询问区间 \([l,r]\) 挂在节点 \(x\) 上,搜索到 \(x\) 时处理关于 \(x\)\(fa_x\) 的询问。 dfs 进入 \(x\) 子树前加入记录询问结果,递归结束前再记录一次,两者之差即为真实答案。

  • \(x\) 的儿子个数 \(\gt B\) ,那么这样的 \(x\) 不超过 \(\frac nB\) 个。

    考虑一次处理关于 \(x\) 的所有询问。将 \(y_i\) 子树中的点看成第 \(i\) 种颜色,我们需要求 \([l,r]\) 中不同颜色出现次数平方和。而这是莫队的经典题,参见P2709 小B的询问

    记有颜色的点总数为 \(n_x\) ,挂在 \(x\) 上的询问个数为 \(m_x\) ,则单次莫队的代价为 \(\mathcal O(n_x\sqrt{m_x})\) 。由于 \(n_x\) 最坏情况下可以到 \(\mathcal O(n)\) ,跑 \(\frac nB\) 轮还是吃不消。

    都来做 Ynoi 了应该不至于连莫队的时间复杂度都分析不清楚吧?

    • 对于左指针:每个询问最多移动 \(B\) 次,总代价 \(\mathcal O(mB)\)
    • 对于右指针:总共 \(\frac nB\) 个块,每块都要从左跑到右,总代价 \(\mathcal O(\frac{n^2}B)\)

    \(B=\frac n{\sqrt m}\) ,可以得到最优时间复杂度 \(\mathcal O(n\sqrt m)\)

    \(x\) 的重儿子为 \(son_x\) ,在上一部分计算 \(f(x,l,r)\)\(f(son_x,l,r)\) 的值,这里只计算轻儿子的平方和。

    根据启发式合并的复杂度计算方法,有 \(\sum n_x=\mathcal O(n\log n)\) 。因此 \(\sum\mathcal O(n_x\sqrt m_x)\le\mathcal O(n\log n\sqrt{m})\) ,实际情况远远跑不满。

    洛谷讨论区中 lxl 在分析这种做法时间复杂度时将 \(\log\) 塞到了根号下面,笔者对此表示怀疑,也欢迎广大读者留下自己的见解。

时间复杂度 \(\mathcal O(n\sqrt n+mB+\frac{n^2}B+n\log n\sqrt m)\) ,理论上取 \(B=\frac n{\sqrt m}\) 最优。

虽然复杂度瓶颈在莫队,但常数瓶颈在二维数点,可以适当将 \(B\) 开小。 由于本题数据不强,实测 \(B=100\) 时单个测试点可以在 \(\texttt{0.5s}\) 内跑完。

如果对每个 \(x\)\(sz\)\(D\) 大的子树用第一种做法,其余用第二种,则 \(\sum n_x=\mathcal O(\log_{D+1}m)\) ,但相应需要 \(\mathcal O(mD)\) 的空间。

上面给出了 \(D=1\) 的做法。虽然取 \(D=\sqrt n\) 时可以将复杂度压到严格根号,但很可惜空间无法承受。

如果追求极致效率,还可以对不同的 \(x\) 分别决策 \(sz\) 前几的 \(y_i\) 用第一种方法,其余用第二种。但是徒增了很多代码量而且平衡点难以把握,所以不是很推荐。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int B=100,maxn=2e5+5;
int m,n,r,cnt;
vector<int> g[maxn];
int id[maxn],sz[maxn],dfn[maxn],son[maxn];
int col[maxn],lsh[maxn];
int bel[maxn],ed[maxn];
ll res[maxn];
struct node
{
    int l,r,id,t1,t2;
};
vector<node> h[maxn];
int read()
{
    int q=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) q=q*10+ch-'0',ch=getchar();
    return q;
}
void dfs1(int u,int f)
{
    sz[u]=1,dfn[u]=++cnt,id[cnt]=u;
    for(auto it=g[u].begin();it!=g[u].end();it++) if(*it==f) {g[u].erase(it);break;}
    for(auto v:g[u])
    {
        dfs1(v,u),sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
namespace block
{
    int a[maxn],s[maxn];
    void add(int x,int v)
    {
        for(int i=x;i<=ed[bel[x]];i++) a[i]+=v;
        for(int i=bel[x]+1;i<=bel[n];i++) s[i]+=v;
    }
    int ask(int x)
    {
        return a[x]+s[bel[x]];
    }
    int ask(int l,int r)
    {
        return ask(r)-ask(l-1);
    }
}
using namespace block;
void dfs2(int u,int f)
{
    for(auto &p:h[u]) p.t1=ask(p.l,p.r);
    if(g[f].size()<=B||u==son[f]) for(auto &p:h[f]) p.t2=ask(p.l,p.r);
    add(u,1);
    for(auto v:g[u]) dfs2(v,u);
    for(auto &p:h[u]) {ll x=ask(p.l,p.r)-p.t1;res[p.id]+=x*x;}
    if(g[f].size()<=B||u==son[f]) for(auto &p:h[f]){ll x=ask(p.l,p.r)-p.t2;res[p.id]-=x*x;}
}
namespace moqueue
{
    int B,n,cnt[maxn];
    ll now;
    void add(int x)
    {
        now+=2*(cnt[x]++)+1;
    }
    void del(int x)
    {
        now-=2*(cnt[x]--)-1;
    }
    void work(int _n,vector<node> &v)
    {
        n=_n,B=max(n/sqrt(v.size()),1.0);
        for(int i=1;i<=n;i++) bel[i]=(i-1)/B+1;
        sort(v.begin(),v.end(),[](node x,node y){return bel[x.l]!=bel[y.l]?bel[x.l]<bel[y.l]:x.r<y.r;});
        int l=1,r=0;
        for(auto &p:v)
        {
            p.l=lsh[p.l-1]+1,p.r=lsh[p.r];
            while(l>p.l) add(col[--l]);
            while(r<p.r) add(col[++r]);
            while(l<p.l) del(col[l++]);
            while(r>p.r) del(col[r--]);
            res[p.id]-=now;
        }
        while(l<=r) del(col[r--]);
    }
}
int main()
{
    n=read(),m=read(),r=read();
    for(int i=1;i<=n-1;i++)
    {
        int u=read(),v=read();
        g[u].push_back(v),g[v].push_back(u);
    }
    dfs1(r,0);
    for(int i=1;i<=m;i++)
    {
        int l=read(),r=read(),x=read();
        res[i]-=l<=x&&x<=r,h[x].push_back({l,r,i});
    }
    for(int i=1;i<=n;i++) bel[i]=(i-1)/450+1;
    for(int i=1;i<=bel[n];i++) ed[i]=min(i*450,n);
    dfs2(r,0);
    for(int u=1;u<=n;u++)
    {
        if(g[u].size()<=B||h[u].empty()) continue;
        memset(col,0,sizeof(col));
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if(v!=son[u]) for(int j=dfn[v];j<dfn[v]+sz[v];j++) col[id[j]]=i+1;
        }
        int len=0;
        for(int i=1;i<=n;i++)
        {
            if(col[i]) col[++len]=col[i];
            lsh[i]=len;
        }
        moqueue::work(len,h[u]);
    }
    for(int i=1;i<=m;i++) printf("%lld\n",res[i]>>1);
    return 0;
}

posted on 2025-09-03 20:23  peiwenjun  阅读(13)  评论(0)    收藏  举报

导航