bzoj 2216 [Poi2011]Lightning Conductor——单调队列+二分处理决策单调性
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2216
那个关于位置的代价是带根号的,所以随着距离的增加而增长变慢;所以靠后的位置一旦比靠前的位置优,就会一直更优(因为距离相同地增长,基数大的增长慢),所以有决策单调性。
一开始写了和 bzoj 4709 一样的实现,就是使得队列里相邻两个位置的 “后一个位置优于前一个位置的时间” 是单调递增的。但是却 TLE 。不知道为什么。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define db double using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } int g[20]; void wrt(int x) { if(!x){puts("0");return;}int t=0; while(x)g[++t]=x%10,x/=10; for(int i=t;i;i--)putchar(g[i]+'0');puts(""); } int Mx(int a,int b){return a>b?a:b;} const int N=5e5+5; int n,a[N],q[N],he,tl,ans[N]; db cal(int k,int bh){ return a[k]+sqrt((bh-k));} int cz(int u,int v) { int l=u,r=n,ret=n+1; while(l<=r) { int mid=l+r>>1; if(cal(u,mid)>=cal(v,mid))ret=mid,r=mid-1; else l=mid+1; } return ret; } int main() { n=rdn(); for(int i=1;i<=n;i++)a[i]=rdn(); for(int i=1;i<=n;i++) { while(tl-he>=2&&cz(q[tl],q[tl-1])>=cz(i,q[tl])) tl--; q[++tl]=i; while(tl-he>=2&&cz(q[he+2],q[he+1])<=i)he++; ans[i]=ceil(cal(q[he+1],i)); } he=tl=0; reverse(a+1,a+n+1); for(int i=1;i<=n;i++) { while(tl-he>=2&&cz(q[tl],q[tl-1])>=cz(i,q[tl])) tl--; q[++tl]=i; while(tl-he>=2&&cz(q[he+2],q[he+1])<=i)he++; ans[n-i+1]=Mx(ans[n-i+1],ceil(cal(q[he+1],i))); } //for(int i=1;i<=n;i++)printf("%d\n",Mx(0,ans[i]-a[n-i+1])); for(int i=1;i<=n;i++)wrt(ans[i]-a[n-i+1]); return 0; }
然后看题解,思路是维护每个决策点能转移给哪段区间。
把之前每个决策点控制的区间放进队列里,每次看看当前决策点 i 能把队尾的哪些区间弹掉(即自己覆盖那些区间,注意自己覆盖的区间应该在自己后面);先判断整个弹掉一个区间,就是看看在区间左端点是不是自己比那个决策点更优(左端点应该在 i 点后面);再在不能整个弹掉的时候判断 i 点能从区间的哪个位置开始往后控制,二分一下即可。
注意算代价应该用 double ,最后再 ceil 。因为过程中 ceil 的话,那个函数图象就不是很好,比如,在二分的时候如果写了 <= 而不是 < ,又有一次 mid 取在了那个 3 和 4 重合的位置,就可能以为下面那个不优的图象在该位置后面是优于上面的。但即使写成 < 而不是 <= ,也还是会 WA , 一定要过程中用 double 才行。
注意把 i 作为决策点插入之后判断一下队首是不是 r > i ,因为插入的时侯可能改了队尾的 r ,即可能改了队首的 r ,所以要在插入之后判断。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define db double using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } db Mx(db a,db b){return a>b?a:b;} const int N=5e5+5; int n,a[N],he,tl; bool fg; db ans[N]; struct Node{ int l,r,p; Node(int l=0,int r=0,int p=0):l(l),r(r),p(p) {} }q[N]; db cal(int p,int k){ return a[p]+sqrt(k-p);} void solve() { he=tl=0; for(int i=1;i<=n;i++) { if(he==tl||cal(q[tl].p,n)<cal(i,n)) { while(he<tl&&q[tl].l>=i&&cal(q[tl].p,q[tl].l)<=cal(i,q[tl].l)) tl--;//q[tl].l>=i if(he<tl) { int l=Mx(i,q[tl].l),r=q[tl].r,ret=q[tl].r+1; //Mx(i,...) //q[tl].r+1 while(l<=r) { int mid=l+r>>1; if(cal(q[tl].p,mid)<cal(i,mid))ret=mid,r=mid-1; else l=mid+1; } q[tl].r=ret-1; if(ret<=n)q[++tl]=Node(ret,n,i);//if } else q[++tl]=Node(i,n,i); } while(he<tl&&q[he+1].r<i)he++;//after!! for r change ans[i]=Mx(ans[i],cal(q[he+1].p,i)); } } int main() { n=rdn(); for(int i=1;i<=n;i++)a[i]=rdn(); solve(); reverse(a+1,a+n+1); reverse(ans+1,ans+n+1); solve();//rev(ans)! for(int i=n;i;i--)printf("%d\n",(int)ceil(ans[i])-a[i]); return 0; }