(Large atural) 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
-
如果初始区间定为 (1sim 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;
}