zoukankan      html  css  js  c++  java
  • 题解 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;
    }
    
  • 相关阅读:
    微信小程序登入实现
    CacheLab实验--深入了解计算机系统实验
    power designer的物理数据模型生成数据字典。
    PowerDesigner15在生成SQL时报错Generation aborted due to errors detected during the verification of the mo
    Mac系统下,如何申请并安装教育版Navicat
    Mac下修改Mysql密码
    数组
    AbstractList源码阅读
    List源码阅读笔记
    AbstractCollection源码阅读笔记
  • 原文地址:https://www.cnblogs.com/BlankAo/p/14012873.html
Copyright © 2011-2022 走看看