题解 P5384-[Cnoi2019]雪松果树
\(\Large\natural\) P5384 [Cnoi2019]雪松果树 / 原题链接
其实这道题就是 Blood Cousins 的超级加强版。
不理解算法的先看看上面的题解,这里不再阐述。
下面开始 毒 瘤 优 化 之 路 。
毒瘤优化之路
更高效地处理询问
Blood Cousins 那道题中,我们用的是 lca 处理询问。然而,这题你的倍增数组就会变得很大,以至于大约是 77MB !
所以我们不能用 lca 了。我们创造一个栈,进行 dfs 时把点存进去,回溯时弹出。这样在点 \(o\) 时,栈依次存的就是 \(o\) 到根的路径上的点。
这样栈中第 \(top-kth\) 项就是点 \(o\) 的第 \(k\) 个祖先了。比如点 \(o\) 存于 \(stack_{top}\) 中,那么点 \(o\) 的父亲( 1-th 祖先)就是 \(stack_{top-1}\),以此类推……
有个坑点,就是入过 \(top-kth<0\) (比如 \(dep_o\) 为5,但要查询它的第 10 个祖先),那么就要直接 continue
,否则可能 RE 。
所以,我们要先创造两个链表,\(lin1_o\) 代表要询问 \(o\) 的 k_th 祖先, \(lin2_o\) 代表所有 “ 对于询问中的点 \(i\) , \(i\) 的 k_th 祖先是 \(o\) ” 的这些询问。(如果用 vector
,您空间炸了)
处理线段树的 l 与 r
-
如果初始区间定为 \(1\sim 10^6\) 会空间爆炸,所以我们找出最大的深度 \(dep_{max}\),令 \(r=dep_{max}\)。
-
不要在结构体里存一个线段树结点的区间(即 \(l\) 与 \(r\)),浪费空间。在递归 dfs 中放 \(l\) 与 \(r\)。比如
void updat(int o,int l,int r,int num)
。
回收线段树合并的空间
(设把 \(o2\) 合并到 \(o1\) 上)
因为 \(o1\) 和 \(o2\) 合并后, \(o2\) 就没用了,它的点可以回收。所以我们建立一个 \(rab\) 数组,表示可以重复利用的点的编号,然后将它们回收利用。
int rab[t7],rabc,cnt;//rab[1~rabc]代表可以重复利用的点
int new_dot(){
if(!rabc){cnt++;return cnt;}
else{rabc--;return rab[rabc+1];}
}
void die_dot(int id){
tre[id]=(elep){0,0,0};
rabc++;rab[rabc]=id;
}
//当需要建立一个新的左儿子时(右儿子同理)
tre[o].lv=new_dot();
//当 merge 中左右子树都递归完了
die_dot(o2);
优先处理大子树
设 \(siz_o\) 为以 \(o\) 为根的子树的点个数。
对于一个点,对于它的所有儿子 \(v\),按照 \(siz_v\) 的大小处理,最先处理 \(siz_v\) 最大的。
因为总的线段树规格总是 \(Segtree_o+Segtree_v\),如果 \(siz_v\) 在后边处理,那么此时 \(Segtree_o\) 可能已经很大,再加上大大的 \(Segtree_v\) ,空间就炸了。
代码具体实现:
在 dfs 中,再次遍历 \(o\) 的边并建立一样新边。这样会导致这些边的编号是连续的。然后就将这一段按照 \(siz\) 排序就好了。注意点 \(o\) 的边的编号从 \(l\) 开始,从 \(r\) 结束,则 \(l、r\) 要用数组存,这样在后来的线段树合并的再次 dfs 中就可以直接找边。
int fstz[n7],lasz[n7],toz[n7],ecnt=0;//fstz是上文的l,lasz是上文的r,toz是某条边的重点
//更换新边这样写
fstz[o]=ecnt+1;
mar(o)ecnt++,toz[ecnt]=v;
lasz[o]=ecnt;
sort(toz+fstz[o],toz+lasz[o]+1,cmp);
//线段树合并的dfs这样写
for(int E=fstz[o];E<=lasz[o];E++){
if(toz[E]==fa)continue;
dfs2(...);
merge(...);
}
其它一些小操作
-
使用快读,因为读入数据有 \(10^6\)
-
不用 pushup !
-
不能默认点 \(o\) 对于的线段树的根节点也是 \(id=o\) 了,这样也会浪费一点空间。我们要一点都不浪费。用 \(rot_o\) 存。
updat(rot[o],1,hug,dep);//放在这里是错误的!
for(int E=fstz[o];E<=lasz[o];E++){
if(toz[E]==fa)continue;
dfs2(toz[E],o,dep+1);
merge(rot[o],rot[ toz[E] ],1,hug);
}
updat(rot[o],1,hug,dep);//放在这里是正确的!
这样才能让 \(Segtree_o\) 在一开始为0 ,从而使得遍历每个 \(v\) 时,有 \(Segtree_o+Segtree_v\) 更小。
终于优化完了!
顺便卡到了最优解第一页
代码
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
using namespace std;
const int n7=1000005,m7=1000005,t7=4020004;
struct dino{int to,nxt;}e[m7];
struct miff{//链表
int lfst[n7],id[n7],nxt[n7],qcnt;
miff () {qcnt=0;}
void qdge(int sta,int idz){
qcnt++;
id[qcnt]=idz,nxt[qcnt]=lfst[sta];
lfst[sta]=qcnt;
}
}lin1,lin2;
struct elep{int lv,rv,val;}tre[t7];
struct galo{int o,ned;}qq[n7];
int n,T,hug,ecnt,fst[n7],toz[n7],fstz[n7],lasz[n7],siz[n7];
int rot[n7],rab[t7],rabc,cnt,sak[n7],top,ans[n7];
int rd(){
int shu=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return shu;
}
void edge(int sta,int edn){
ecnt++;
e[ecnt]=(dino){edn,fst[sta]};
fst[sta]=ecnt;
}
int new_dot(){
if(!rabc){cnt++;return cnt;}
else{rabc--;return rab[rabc+1];}
}
void die_dot(int id){
tre[id]=(elep){0,0,0};
rabc++;rab[rabc]=id;
}
void updat(int o,int l,int r,int num){
if(l==r){tre[o].val++;return;}
int mid=(l+r)>>1;
if(num<=mid){
if(!tre[o].lv)tre[o].lv=new_dot();
updat(tre[o].lv,l,mid,num);
}
if(mid+1<=num){
if(!tre[o].rv)tre[o].rv=new_dot();
updat(tre[o].rv,mid+1,r,num);
}
}
int merge(int o1,int o2,int l,int r){
if(!o2)return o1;
if(!o1)return o2;
if(l==r){tre[o1].val+=tre[o2].val;return o1;}
int mid=(l+r)>>1;
tre[o1].lv=merge(tre[o1].lv,tre[o2].lv,l,mid);
tre[o1].rv=merge(tre[o1].rv,tre[o2].rv,mid+1,r);
die_dot(o2);
return o1;
}
int query(int o,int l,int r,int num){
if(!o)return 0;
if(l==r)return tre[o].val;
int mid=(l+r)>>1;
if(num<=mid) return query(tre[o].lv,l,mid,num);
if(mid+1<=num)return query(tre[o].rv,mid+1,r,num);
}
void dfs2(int o,int fa,int dep){
if(!rot[o])rot[o]=new_dot();
for(int E=fstz[o];E<=lasz[o];E++){
if(toz[E]==fa)continue;
dfs2(toz[E],o,dep+1);
merge(rot[o],rot[ toz[E] ],1,hug);
}
updat(rot[o],1,hug,dep);
for(int Q=lin2.lfst[o];Q;Q=lin2.nxt[Q]){
ans[ lin2.id[Q] ]=query(rot[o],1,hug,qq[ lin2.id[Q] ].ned)-1;
}
}
bool cmp(int p,int q){return siz[p]>siz[q];}
void dfs1(int o,int fa,int dep){
hug=max(hug,dep);
top++,sak[top]=o;
for(int Q=lin1.lfst[o];Q;Q=lin1.nxt[Q]){
int z=top-qq[ lin1.id[Q] ].ned;
if(z<=0)continue;
lin2.qdge(sak[z],lin1.id[Q]);
qq[ lin1.id[Q] ].ned+=z;
}
siz[o]=1;
mar(o){
if(v==fa)continue;
dfs1(v,o,dep+1);
siz[o]+=siz[v];
}
top--;
fstz[o]=ecnt+1;
mar(o)ecnt++,toz[ecnt]=v;
lasz[o]=ecnt;
sort(toz+fstz[o],toz+lasz[o]+1,cmp);
}
int main(){
n=rd(),T=rd();
rep(i,2,n)edge(rd(),i);
ecnt=0;
rep(i,1,T){
qq[i]=(galo){rd(),rd()};
lin1.qdge(qq[i].o,i);
}
dfs1(1,0,1);
dfs2(1,0,1);
rep(i,1,T)printf("%d ",ans[i]);
return 0;
}