P3515 [POI2011]Lightning Conductor 四边形不等式
P3515 [POI2011]Lightning Conductor 四边形不等式
1 讲解
按道理说这不应该算一个 dp 了,但是满足决策单调性。
首先先把绝对值去掉,因为如果 \(j\) 比 \(i\) 大我们可以反着做一遍。以下认为 \(j\le i\) 。
设 \(w(j,i)=\sqrt{i-j}\) ,我们把不等式移项后可以得到:
我们证明 \(w\) 满足四边形不等式:
-
证明:
-
\(\forall a< c,a+1\le c\),那么有:
\[w(a,c)+w(a+1,c+1)=2\times \sqrt{c-a}\\ w(a+1,c)+w(a,c+1)=\sqrt{c-a-1}+\sqrt{c-a+1} \]我们用一式减去二式可以得到:
\[2\times \sqrt{c-a}-\sqrt{c-a-1}-\sqrt{c-a+1} \]令 \(d=c-a\) 可以得到:
\[2\sqrt{d}-\sqrt{d-1}-\sqrt{d+1}=(\sqrt{d}-\sqrt{d+1})-(\sqrt{d-1}-\sqrt{d}) \]不难发现因为根号函数增速减缓,所以上面这个式子恒大于等于 \(0\) 。
所以我们可以得到:
\[w(a,c)+w(a+1,c+1)\ge w(a+1,c)+w(a,c+1) \]
不难发现,这与我们的四边形不等式符号想法,但是这个题,很明显有:
注意这里是取最大值而不是四边形不等式里取最小值,所以这个东西仍然可以证明满足决策单调性,只需要像我们证明满足四边形不等式就满足取 \(\min\) 的决策单调性是一样的。
2 代码实现
在这里使用单调队列作为二分栈来做的,对于每一个决策,维护一个区间,表示在这个区间里这个决策最优。
注意到在这个题中,我们要保证 \(w\) 要有定义,同时因为自己本身也是一个决策,所以要先入队,在决策。
剩下的我们看着代码来讲解。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 500010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){
return a>b?a:b;
}
struct node{
int k,l,r;
inline node(){}
inline node(int k,int l,int r) : k(k),l(l),r(r) {}
};
node q[N];
int n,a[N],head,tail;
dd f[N],sqr[N];
inline dd calc(int id,int k){
return (dd)a[k]+sqr[id-k];
}
inline int erfen(int k,int l,int r){
while(l<r){
int mid=(l+r)>>1;
if(calc(mid,q[tail].k)>calc(mid,k)) l=mid+1;
else r=mid;
}
return l;
}
inline void insert(int k){
if(head==tail){q[++tail]=node(k,1,n);return;}
int pos;
while(head<tail&&q[tail].l>=k&&calc(q[tail].l,q[tail].k)<calc(q[tail].l,k)){
tail--;
}
if(q[tail].r>=k&&calc(q[tail].r,q[tail].k)>calc(q[tail].r,k)){
if(q[tail].r!=n) pos=q[tail].r+1;
else return;
}
else pos=erfen(k,Max(q[tail].l,k),q[tail].r);
q[tail].r=pos-1;q[++tail]=node(k,pos,n);
}
inline void solve(int e){
head=tail=0;insert(1);
for(int i=1;i<=n;i++){
if(head<tail&&q[head+1].r<=i-1) head++;
if(head<tail){
int k=q[head+1].k;
f[i]=Max(f[i],calc(i,k));
}
if(i!=n) insert(i+1);
}
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) {scanf("%lld",&a[i]);sqr[i]=sqrt(i);}
solve(1);
reverse(a+1,a+n+1);reverse(f+1,f+n+1);
solve(2);
for(int i=n;i>=1;i--) printf("%lld\n",(int)ceil(f[i])-a[i]);
return 0;
}
每次我们把决策点在 \(i\) 左边的决策去掉,这样每次就可以直接取出队首来转移,insert
函数支持插入一个决策。
首先如果队列为空的话,直接把决策加进去。否则,我们在序列左侧有意义的情况下检查并判断是否要弹出队尾。
我们检查当前插入决策是否比队尾决策要劣,如果在最右边还劣的话,根据决策单调性,当前决策只能去覆盖后边。
否则我们在这段里面二分,注意维护 \(w\) 函数是否有意义。
变量千万千万不能写重 !!!
其实这个东西还可以二分去做,就是说我们先暴力求出 \(mid\) 的最优决策,然后就变成了两个子问题,随时维护决策区间和待决策区间就可以。
分治在某些时候可能会被卡,所以尽量还是不要写。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define Re register int
#define ull unsigned long long
#define N 500010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,a[N];
dd f[N],sqr[N],tmp;
inline void solve(int l,int r,int z,int y){
if(l>r) return;
int mid=(l+r)>>1,j0;dd maxx=-INF;
for(int i=z;i<=mid&&i<=y;i++){
dd now=a[i]+sqr[mid-i];if(now>maxx) maxx=now,j0=i;
}
f[mid]=max(f[mid],maxx);
solve(l,mid-1,z,j0);solve(mid+1,r,j0,y);
}
int main(){
read(n);for(int i=1;i<=n;i++) read(a[i]),sqr[i]=sqrt(i);
solve(1,n,1,n);
reverse(a+1,a+n+1);reverse(f+1,f+n+1);
solve(1,n,1,n);
for(int i=n;i>=1;i--) printf("%d\n",(int)ceil(f[i])-a[i]);
}