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\) 子树的点数,则:
其中 \(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;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/19072397
浙公网安备 33010602011771号