题解 P5384-[Cnoi2019]雪松果树

(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

  1. 如果初始区间定为 (1sim 10^6) 会空间爆炸,所以我们找出最大的深度 (dep_{max}),令 (r=dep_{max})

  2. 不要在结构体里存一个线段树结点的区间(即 (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(...);
}

其它一些小操作

  1. 使用快读,因为读入数据有 (10^6)

  2. 不用 pushup !

  3. 不能默认点 (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;
}
原文地址:https://www.cnblogs.com/BlankAo/p/14012873.html