[洛谷P3261] [JLOI2015]城池攻占(左偏树)
不得不说,这道题目是真的难,真不愧它的“省选/NOI-”的紫色大火题!!!
花了我晚自习前半节课看题解,写代码,又花了我半节晚自习调代码,真的心态爆炸。基本上改得和题解完全一样了我才过了这道题!真的烦。没事,那接下来我来完全把这道题搞透。
Part 1 理解题目
至少我一开始不知道为什么要用左偏树,甚至我看题解一开始也都没弄懂,所以先把题目弄清楚。
首先我们由题可以知道,这要求我们从建好的树的叶子节点开始往上推,有些骑士到特定的点才会出现,check一下骑士能否攻占城池,再记录进答案,更新战斗力,这就很容易想到左偏树可并堆了。
Part 2 解题思想
既然每到一个点会出现一堆的新骑士,所以我们可以在那些点连一些“隐藏边”,到这个点时用链式前向星扫一遍加到一个小根堆中,然后把这个点以下的所有剩下的骑士合并到这个堆中(板子),然后在check时挨个弹出堆顶,如果不能占领就记入答案,能占领我们就要考虑更新骑士,我们不可能直接更新整个堆中的骑士,这样会被硬生生卡成O(n)的修改,所以我们考虑放一个lazy标记在堆顶,每一次合并和删除的时候再下放就可以了。
part 3 code
#include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #include<stack> #define lst long long #define rg register #define N 300050 using namespace std; int n,m,cnt; bool type[N]; int fir[N],deep[N],up[N],dead[N]; lst key[N],def[N],v[N],mul[N],plu[N]; struct edge{ int to,nxt; }a[N],b[N]; int head[N],ft[N],ls[N],rs[N],dis[N]; inline lst read() { rg lst s=0,m=1;rg char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')m=-1,ch=getchar(); while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar(); return m*s; } void cover(rg int A,rg lst c,rg lst j) { if(!A)return; key[A]*=c,key[A]+=j; mul[A]*=c,plu[A]*=c,plu[A]+=j; } void pushdown(rg int A) { cover(ls[A],mul[A],plu[A]); cover(rs[A],mul[A],plu[A]); mul[A]=1,plu[A]=0; } int Merge(rg int A,rg int B) { if(!A||!B)return A+B; if(key[A]>key[B])swap(A,B); pushdown(A),pushdown(B); rs[A]=Merge(rs[A],B); if(dis[ls[A]]<dis[rs[A]])swap(ls[A],rs[A]); dis[A]=dis[rs[A]]+1; return A; } int Delete(rg int A) { pushdown(A); return Merge(ls[A],rs[A]); } int dfs(rg int now,rg int fm) { rg int A=0,B; deep[now]=deep[fm]+1; for(rg int i=ft[now];i;i=b[i].nxt)A=Merge(A,b[i].to); for(rg int i=head[now];i;i=a[i].nxt) { B=dfs(a[i].to,now); A=Merge(A,B); } while(key[A]<def[now]&&A) { dead[now]++;up[A]=deep[now]; A=Delete(A); } if(type[now])cover(A,v[now],0); else cover(A,1,v[now]); return A; } int main() { n=read(),m=read(); for(rg int i=1;i<=n;++i)def[i]=read(); for(rg int i=2;i<=n;++i) { rg int go=read(); a[++cnt]=(edge){i,head[go]};head[go]=cnt; type[i]=read(),v[i]=read(); }cnt=0; for(rg int i=1;i<=m;++i) { key[i]=read(),fir[i]=read(); b[++cnt]=(edge){i,ft[fir[i]]};ft[fir[i]]=cnt; } dfs(1,0); for(rg int i=1;i<=n;++i)printf("%d\n",dead[i]); for(rg int i=1;i<=m;++i)printf("%d\n",deep[fir[i]]-up[i]); return 0; }
到此为止,顺便膜拜一下大佬zsy,这是他的城池攻占:666
哪怕人间是炼狱,梦想永远是天堂
继续走下去吧,理想永远都年轻,花儿一定会再次盛开