bzoj2216[POI2011] Lightning Conductor
题意:
已知一个长度为n的序列a1,a2,...,an。
对于每个1<=i<=n,找到最小的非负整数p满足
对于任意的j, aj < = ai + p - sqrt(abs(i-j))
n<=500000
决策单调性的新写法get
首先暴力的做法是对于每个i枚举每个j,方便起见分别考虑j在i左侧和右侧的情况以去掉绝对值。不妨只考虑j在i左侧,右侧是对称的.
那么我们只需要在每个ai的左侧找到一个aj使得aj+sqrt(i-j)最大,由这个aj确定此处p的值(右侧也要对称地找一遍)
然后我们发现这是一个1D1D动态规划,这题范围这么大就猜测是斜率优化或者决策单调性,打表发现有决策单调性.
决策单调性有两种nlogn的写法,一种是单调栈+二分(《1D1D动态规划优化初步》里头的方法),一种是分治,假如我们知道f[l],f[l+1]….f[r-1],f[r]这些状态的决策点在区间[L,R]内,我们就可以取[l,r]区间的中点mid,暴力出f[mid]的决策g,然后就知道f[l]…f[mid-1]这些状态的决策点在区间[L,g]内,f[mid+1]…f[r]这些状态的决策点在区间[g,R]内,就可以分治了。同一深度的所有暴力循环找决策的复杂度加起来是O(n),又因为分治的时候mid的选取是均分的所以深度是logn,于是这个分治做法也是O(nlogn)的,注意这道题分治的时候有个边界的处理,具体看代码.
虽然我也想知道为什么,但我们联赛之前考过这道题…嗯,我当时还考场AC了…囧…
当时写的弱鸡证明(可能有错,欢迎捉虫):
不妨讨论j在i左侧的情况。
假设j1<j2<i1<i2,我们只需证明:如果j2在i1处比j1优,那么j2在i2处也比j1优。
也就是说:我们需要从aj1+sqrt(i1-j1)>aj2+sqrt(i1-j2) 推出aj1+sqrt(i2-j1)>aj2+sqrt(i2-j2)
不妨将等式的左右两边都看作关于i的函数,那么上面两个式子分别比较了两个函数在i1和i2处的大小。我们需要考虑这两个函数在自变量i1变为i2时函数值的变化。
当我们把左侧aj1+sqrt(i1-j1)换为aj1+sqrt(i2-j1),这个式子增加了sqrt(i2-j1)-sqrt(i1-j1)
当我们把右侧aj2+sqrt(i1-j2)换为aj2+sqrt(i2-j2),这个式子增加了sqrt(i2-j2)-sqrt(i1-j2)
考虑y=sqrt(x)这个函数,它有一个性质:x越大,y随x的增长越慢,也就是说图像趋于平缓.因此,如果j1>j2,我们可以直观地认为sqrt(i2-j1)-sqrt(i1-j1)> sqrt(i2-j2)-sqrt(i1-j2),因为i2-j1和i1-j1的差值为i2-i1。i2-j2和i1-j2的差值也为i2-i1,而i2-j1比i2-j2小。
更加严谨的证明:可以把sqrt(i2-j1)-sqrt(i1-j1)用平方差公式化为(i2-i1)/(sqrt(i2-j1)+sqrt(i1-j1)),然后显然可以比较两边增加量的大小。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int maxn=500005; int a[maxn]; int s[maxn],l[maxn],r[maxn],top,pt; double f1[maxn],f2[maxn]; int binary(int l,int r,int x,int y){ while(l<=r){ int mid=(l+r)>>1; if(y>=mid||sqrt(mid-y)+a[y]<sqrt(mid-x)+a[x])l=mid+1; else r=mid-1; } return l; } int main(){ int n;scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i]); s[++top]=1;l[top]=2;r[top]=n;int pt=1; for(int i=2;i<=n;++i){ while(r[pt]<i)++pt;//printf("%d\n",s[pt]); f1[i]=sqrt(i-s[pt])+a[s[pt]]-a[i]; while(top&&l[top]>i&&(sqrt(l[top]-i)+a[i])>(sqrt(l[top]-s[top])+a[s[top]]))top--; int tmp=binary(l[top],r[top],s[top],i); if(tmp==r[top]+1)continue; r[top]=tmp-1;s[++top]=i;l[top]=tmp;r[top]=n; } for(int i=1;i*2<=n;++i){ swap(a[i],a[n-i+1]); } top=0; s[++top]=1;l[top]=2;r[top]=n;pt=1; for(int i=2;i<=n;++i){ while(r[pt]<i)++pt;//printf("%d\n",s[pt]); f2[i]=sqrt(i-s[pt])+a[s[pt]]-a[i]; while(top&&l[top]>i&&(sqrt(l[top]-i)+a[i])>(sqrt(l[top]-s[top])+a[s[top]]))top--; int tmp=binary(l[top],r[top],s[top],i); r[top]=tmp-1;s[++top]=i;l[top]=tmp;r[top]=n; } for(int i=1;i*2<=n;++i){ swap(f2[i],f2[n-i+1]); } for(int i=1;i<=n;++i)printf("%.0f\n",max(0.0,ceil(max(f1[i],f2[i])))); return 0; }
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int maxn=500005; int a[maxn]; double f1[maxn],f2[maxn]; void solve1(int l,int r,int L,int R){ if(l>r)return; int mid=(l+r)>>1; int g=0;double tmp; f1[mid]=a[mid];//有可能j!=mid时得不到大于0的p,所以先给f初始一个值 for(int i=L;i<=R&&i<mid;++i){ if((tmp=a[i]+sqrt(mid-i))>f1[mid])f1[mid]=tmp,g=i; } if(g==0)g=mid; f1[mid]-=a[mid]; solve1(l,mid-1,L,g);solve1(mid+1,r,g,R); } void solve2(int l,int r,int L,int R){ if(l>r)return; int mid=(l+r)>>1; int g=0;double tmp; f2[mid]=a[mid]; for(int i=R;i>=L&&i>mid;--i){ if((tmp=a[i]+sqrt(i-mid))>f2[mid])f2[mid]=tmp,g=i; } if(g==0)g=mid; f2[mid]-=a[mid]; solve2(l,mid-1,L,g);solve2(mid+1,r,g,R); } int main(){ int n;scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); } solve1(1,n,1,n); solve2(1,n,1,n); for(int i=1;i<=n;++i)printf("%.0f\n",ceil(max(f1[i],f2[i]))); return 0; }