P9999 Ynoi2000 tmostnrq
P9999 Ynoi2000 tmostnrq
lxl 大神上课时讲的做法(膜拜),太菜的我调了 3 天……
目前最优解,貌似有很多抄题解的(害怕。
思路
将询问离线下来,做扫描线。
当操作向
考虑进行树剖,下图中一条黑线代表一条重链。
假设向红色点进行靠拢。
橙色线覆盖部分的重链向下跳,其余向上。
暴力的找到包含橙色部分的重链,将重链连接处向下跳会切换重链的点(绿色箭头)手动下移。
橙色线段为覆盖的部分和重链,执行整体上移,并且将向上跳会切换重链的点(蓝色箭头),手动上移。显然不可以一个个重链遍历,考虑维护一个堆,堆里面存每个链要向上跳的点什么时刻会向上切换重链,每次取堆顶维护重链。
考虑在链上的数据结构,支持部分/整体上移下移即对权值
由于需要合并权值相同点,每个询问用并查集定位在平衡树上点的编号,当询问区间结束时删除一个点的标记。
考虑好每次向下跳时切换和向上跳时的操作,这题就解决了。
实现细节
一个整体的时刻,堆按照发生向上跳切换链的时刻排序,平衡树中按照到链顶的距离作为二叉搜索树的权值。
由于每个时刻无法一个个更新平衡树,对平衡树进行操作前需要更新平衡树的权值。
//更改时间标记
//rt[x] 是 x 的平衡树的编号
//rttag[x] 存的是上一次更新平衡树 x 的时刻
inline void chgtag(int x)
{
if(!rt[x]) {rttag[x]=tag;return ;}
tr[rt[x]].val-=(tag-rttag[x]);tr[rt[x]].tag-=(tag-rttag[x]);
//向上跳了 tag-rttag[x] 步
rttag[x]=tag;
}
更新完权值后,还需要在堆中先删去平衡树 x 的贡献,在平衡树操作完后再加入。
//更改堆
//rts[x] 记录了 x 平衡树在堆中插入的值
inline void sera(int x)
{
chgtag(x);
if(!rt[x]||rts[x]==-1) return ;
frt[rt[x]]=0;
s.erase({rts[x],x});
rts[x]=-1;frt[rt[x]]=x;
}
inline void sins(int x)
{
chgtag(x);
if(!rt[x]) return ;
frt[rt[x]]=0;
int tmp=frlch(rt[x]);//找到平衡树中最左的节点
s.insert({tr[tmp].val+tag+flg,x});//flg 是一个操作参数下文提到
rts[x]=tr[tmp].val+tag+flg;frt[rt[x]]=x;
}
在平衡树中插入时,必须要合并权值相同的点,否则复杂度会退化,在后面分析复杂度时也会提到。
//向重链对应平衡树中插入数
inline void ins(int x,int res)
{
chgtag(x);
int tmp=0;frt[rt[x]]=0;//frt[x] 表示平衡树中 x 所对应的原树中的链顶
sera(x);
spiltval(rt[x],rt[x],tmp,tr[res].val);
int tmp1=frrch(rt[x]);//找最右的点
if(tr[tmp1].val==tr[res].val)//合并权值相同的点
{
spiltsiz(rt[x],rt[x],tmp1,tr[rt[x]].sz-tr[tmp1].sum);
tr[tmp1].sum+=tr[res].sum,tr[tmp1].sz+=tr[res].sum,F.merge(tmp1,res);
//F.merge 是并查集中的操作
merge(rt[x],rt[x],tmp1);
}
else merge(rt[x],rt[x],res);//没有相同权值,直接加入平衡树
merge(rt[x],rt[x],tmp);
sins(x);
frt[rt[x]]=x;
}
删除时有两种情况,一种删除点的一个标记,另一种整个点要从树上删除,以供后面插入到另外一颗树。
//重链对应平衡树中删除数
//flg=1 表示删除一个标记,flg=0 表示整个点从树上删除
inline int era(int x,int sum,int flg)//删除位于 sum+1 位置的点
{
chgtag(x);
frt[rt[x]]=0;
int tmp=0,tmp1=0;
sera(x);
spiltsiz(rt[x],rt[x],tmp,sum);
spiltsiz(tmp,tmp,tmp1,tr[frlch(tmp)].sum);
if(flg)
{
tr[tmp].sum--;
if(tr[tmp].sum) merge(tmp1,tmp,tmp1);
}
merge(rt[x],rt[x],tmp1);
sins(x);
frt[rt[x]]=x;
return tmp;//返回删除点的编号(后面加入另外一棵平衡树可能要用)
}
插入一个标记,与删除一个标记。
//插入删除标记
inline int add(int x)
{
int res=newnode(dep[x]-dep[tp[x]]);
ins(tp[x],res);
return res;
}
inline int del(int x)
{
stack<int>stk;
int p=x,sum=tr[lch(x)].sz,tmp=0;
while(p) stk.push(p),p=tr[p].fa;
p=stk.top();tmp=frt[p];
while(p!=x)
{
if(rch(p)==stk.top()) sum+=tr[lch(p)].sz+tr[p].sum;
p=stk.top();stk.pop();
}
//定位 x 是平衡树上 sum+1 位置的点
//tmp 求出 x 对应原树上的点
era(tmp,sum,1);
return lik[tmp][tr[x].val];
}
核心部分,向
//向 u 集中
inline void work(int u)
{
int x=u,lst=0;flg=1;//由于时刻 tag 目前不需要更新,需要使用 flg 修正加入堆的点的时刻
//lst 是上次的重链顶
while(x)
{
int tx=tp[x],val=dep[x]-dep[tx]-(u==x),tmp=0,tmp1=0;
sera(tx);
frt[rt[tx]]=0;
chgtag(tx);
spiltval(rt[tx],rt[tx],tmp,val);
//取出距离连顶小于等于 val 的点
if(rt[tx]) tr[rt[tx]].val++,tr[rt[tx]].tag++;//整体下移
if(u==x) spiltval(tmp,tmp1,tmp,val+1);//特判 u 位置的点
if(tmp) tr[tmp].val--,tr[tmp].tag--;//整体上移
if(u==x) merge(tmp,tmp1,tmp);
tmp1=frrch(rt[tx]);
//这里使用 while 是因为平衡树合并了大部分权值相同的点,但无法避免一些点没有被合并,如 merge 操作
while(tr[tmp1].val>val&&u!=x)//判断是否要下移
{
era(tx,tr[rt[tx]].sz-tr[tmp1].sum,0);
tr[tmp1].val=tr[tmp1].tag=0;tr[tmp1].sz=tr[tmp1].sum;
ins(lst,tmp1);
tmp1=frrch(rt[tx]);
}
merge(rt[tx],rt[tx],tmp);
sins(tx);
frt[rt[tx]]=tx;lst=tx;x=fa[tx];
}
flg=0;x=u;
while(x) rttag[tp[x]]=tag+1,x=fa[tp[x]];//重链上的点整体更新时刻标记
tag++;
while(!s.empty())//堆向上跳
{
auto it=s.begin();if(it->fi>=tag) break;
int res=it->se,tmp=era(res,0,0);
tr[tmp].val=dep[fa[res]]-dep[tp[fa[res]]];tr[tmp].tag=0;tr[tmp].sz=tr[tmp].sum;
ins(tp[fa[res]],tmp);
}
}
至此,我们的所有非模板操作已经完成,在主函数中调用即可。
时间复杂度分析
每个询问的点最多向上跳
每次移动
如果平衡树不执行合并相同点,复杂度回退化为
所以最后总复杂度在
CODE
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fi first
#define se second
const int maxn=1e6+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;
int n,n0,m,flg;
int a[maxn],ans[maxn];
namespace fhq_treap
{
#define lch(p) tr[p].lch
#define rch(p) tr[p].rch
int tot;
struct fhqnode{int pri,val,sz,lch,rch,tag,fa,sum;}tr[maxn];
inline int newnode(int val){tr[++tot].pri=rand();tr[tot].sz=1;tr[tot].val=val;tr[tot].sum=1;return tot;}
inline void updata(int p)
{
tr[p].sz=tr[lch(p)].sz+tr[rch(p)].sz+tr[p].sum;
if(lch(p)) tr[lch(p)].fa=p;
if(rch(p)) tr[rch(p)].fa=p;
tr[p].fa=0;
}
inline void push_down(int p)
{
if(!tr[p].tag) return ;
if(lch(p)) tr[lch(p)].val+=tr[p].tag,tr[lch(p)].tag+=tr[p].tag;
if(rch(p)) tr[rch(p)].val+=tr[p].tag,tr[rch(p)].tag+=tr[p].tag;
tr[p].tag=0;
}
inline void merge(int &p,int x,int y)
{
if(!x||!y) {p=x^y;return ;}
push_down(x),push_down(y);
if(tr[x].pri<tr[y].pri) p=y,merge(lch(p),x,lch(y));
else p=x,merge(rch(p),rch(x),y);
updata(p);
}
inline void spiltval(int p,int &x,int &y,int k)
{
if(!p) {x=y=0;return ;}
push_down(p);
if(tr[p].val<=k) x=p,spiltval(rch(p),rch(x),y,k),updata(x);
else y=p,spiltval(lch(p),x,lch(y),k),updata(y);
}
inline void spiltsiz(int p,int &x,int &y,int k)
{
if(!p) {x=y=0;return ;}
push_down(p);
if(tr[lch(p)].sz+tr[p].sum<=k) x=p,spiltsiz(rch(p),rch(x),y,k-(tr[lch(p)].sz+tr[p].sum)),updata(x);
else y=p,spiltsiz(lch(p),x,lch(y),k),updata(y);
}
inline int frrch(int p){while(rch(p)) push_down(p),p=rch(p);return p;}
inline int frlch(int p){while(lch(p)) push_down(p),p=lch(p);return p;}
}
using namespace fhq_treap;
namespace Tree_Div
{
int tag;
set<pii>s;
int siz[maxn],fa[maxn],tp[maxn],dep[maxn],hso[maxn],rt[maxn],frt[maxn],rttag[maxn],rts[maxn];
vector<int>lik[maxn];
inline void dfs_pre(int u,int f)
{
dep[u]=dep[f]+1,fa[u]=f;siz[u]=1;
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;dfs_pre(v,u);
siz[u]+=siz[v];
if(siz[hso[u]]<siz[v]) hso[u]=v;
}
}
inline void dfs(int u,int top)
{
tp[u]=top;lik[top].emplace_back(u);
if(hso[u]) dfs(hso[u],top);
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(v==hso[u]) continue;
dfs(v,v);
}
}
}
using namespace Tree_Div;
struct DSU
{
int f[maxn];
inline void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
inline int fr(int x){return f[x]==x?x:f[x]=fr(f[x]);}
inline void merge(int x,int y){int fx=fr(x),fy=fr(y);if(fx!=fy) f[fy]=fx;}
}F;
//更改时间标记
//rt[x] 是 x 的平衡树的编号
//rttag[x] 存的是上一次更新平衡树 x 的时刻
inline void chgtag(int x)
{
if(!rt[x]) {rttag[x]=tag;return ;}
tr[rt[x]].val-=(tag-rttag[x]);tr[rt[x]].tag-=(tag-rttag[x]);
//向上跳了 tag-rttag[x] 步
rttag[x]=tag;
}
//更改堆
//rts[x] 记录了 x 平衡树在堆中插入的值
inline void sera(int x)
{
chgtag(x);
if(!rt[x]||rts[x]==-1) return ;
frt[rt[x]]=0;
s.erase({rts[x],x});
rts[x]=-1;frt[rt[x]]=x;
}
inline void sins(int x)
{
chgtag(x);
if(!rt[x]) return ;
frt[rt[x]]=0;
int tmp=frlch(rt[x]);//找到平衡树中最左的节点
s.insert({tr[tmp].val+tag+flg,x});//flg 是一个操作参数下文提到
rts[x]=tr[tmp].val+tag+flg;frt[rt[x]]=x;
}
//向重链对应平衡树中插入数
inline void ins(int x,int res)
{
chgtag(x);
int tmp=0;frt[rt[x]]=0;
sera(x);
spiltval(rt[x],rt[x],tmp,tr[res].val);
int tmp1=frrch(rt[x]);
if(tr[tmp1].val==tr[res].val)
{
spiltsiz(rt[x],rt[x],tmp1,tr[rt[x]].sz-tr[tmp1].sum);
tr[tmp1].sum+=tr[res].sum,tr[tmp1].sz+=tr[res].sum,F.merge(tmp1,res);
//F.merge 是并查集中的操作
merge(rt[x],rt[x],tmp1);
}
else merge(rt[x],rt[x],res);
merge(rt[x],rt[x],tmp);//插入 res
sins(x);
frt[rt[x]]=x;
}
//重链对应平衡树中删除数
//flg=1 表示删除一个标记,flg=0 表示整个点从树上删除
inline int era(int x,int sum,int flg)//删除位于 sum+1 位置的点
{
chgtag(x);
frt[rt[x]]=0;
int tmp=0,tmp1=0;
sera(x);
spiltsiz(rt[x],rt[x],tmp,sum);
spiltsiz(tmp,tmp,tmp1,tr[frlch(tmp)].sum);
if(flg)
{
tr[tmp].sum--;
if(tr[tmp].sum) merge(tmp1,tmp,tmp1);
}
merge(rt[x],rt[x],tmp1);
sins(x);
frt[rt[x]]=x;
return tmp;//返回删除点的编号(后面加入另外一棵平衡树可能要用)
}
//插入删除标记
inline int add(int x)
{
int res=newnode(dep[x]-dep[tp[x]]);
ins(tp[x],res);
return res;
}
inline int del(int x)
{
stack<int>stk;
int p=x,sum=tr[lch(x)].sz,tmp=0;
while(p) stk.push(p),p=tr[p].fa;
p=stk.top();tmp=frt[p];
while(p!=x)
{
if(rch(p)==stk.top()) sum+=tr[lch(p)].sz+tr[p].sum;
p=stk.top();stk.pop();
}
//定位 x 是平衡树上 sum+1 位置的点
//tmp 求出 x 对应原树上的点
era(tmp,sum,1);
return lik[tmp][tr[x].val];
}
//向 u 集中
inline void work(int u)
{
int x=u,lst=0;flg=1;//由于时刻 tag 目前不需要更新,需要使用 flg 修正加入堆的点的时刻
//lst 是上次的重链顶
while(x)
{
int tx=tp[x],val=dep[x]-dep[tx]-(u==x),tmp=0,tmp1=0;
sera(tx);
frt[rt[tx]]=0;
chgtag(tx);
spiltval(rt[tx],rt[tx],tmp,val);
//取出距离连顶小于等于 val 的点
if(rt[tx]) tr[rt[tx]].val++,tr[rt[tx]].tag++;//整体下移
if(u==x) spiltval(tmp,tmp1,tmp,val+1);//特判 u 位置的点
if(tmp) tr[tmp].val--,tr[tmp].tag--;//整体上移
if(u==x) merge(tmp,tmp1,tmp);
tmp1=frrch(rt[tx]);
//这里使用 while 是因为平衡树合并了大部分权值相同的点,但无法避免一些点没有被合并,如 merge 操作
while(tr[tmp1].val>val&&u!=x)//判断是否要下移
{
era(tx,tr[rt[tx]].sz-tr[tmp1].sum,0);
tr[tmp1].val=tr[tmp1].tag=0;tr[tmp1].sz=tr[tmp1].sum;
ins(lst,tmp1);
tmp1=frrch(rt[tx]);
}
merge(rt[tx],rt[tx],tmp);
sins(tx);
frt[rt[tx]]=tx;lst=tx;x=fa[tx];
}
flg=0;x=u;
while(x) rttag[tp[x]]=tag+1,x=fa[tp[x]];//重链上的点整体更新时刻标记
tag++;
while(!s.empty())//堆向上跳
{
auto it=s.begin();if(it->fi>=tag) break;
int res=it->se,tmp=era(res,0,0);
tr[tmp].val=dep[fa[res]]-dep[tp[fa[res]]];tr[tmp].tag=0;tr[tmp].sz=tr[tmp].sum;
ins(tp[fa[res]],tmp);
}
}
vector<pii>vec[2][maxn];
int main()
{
tr[0].val=-1;
n=read(),n0=read(),m=read();
for(int i=2;i<=n;i++) fa[i]=read(),T.add(fa[i],i);
for(int i=1;i<=n0;i++) a[i]=read();
for(int i=1;i<=m;i++)
{
int l,r,x;
l=read(),r=read(),x=read();
vec[0][l].push_back({x,i});
vec[1][r].push_back({x,i});
}
dfs_pre(1,0);dfs(1,1);
F.init(m);
for(int i=1;i<=n0;i++)
{
for(auto v:vec[0][i]) ans[v.se]=add(v.fi);
work(a[i]);
for(auto v:vec[1][i]) ans[v.se]=del(F.fr(ans[v.se]));
}
for(int i=1;i<=m;i++) write(ans[i]),putchar('\n');
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现