Loading

P3515 [POI2011]Lightning Conductor 四边形不等式

P3515 [POI2011]Lightning Conductor 四边形不等式

链接

1 讲解

按道理说这不应该算一个 dp 了,但是满足决策单调性。

首先先把绝对值去掉,因为如果 \(j\)\(i\) 大我们可以反着做一遍。以下认为 \(j\le i\)

\(w(j,i)=\sqrt{i-j}\) ,我们把不等式移项后可以得到:

\[p\ge a_j+\sqrt{i-j}+a_i=a_j+w(j,i)+a_i \]

我们证明 \(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) \]

不难发现,这与我们的四边形不等式符号想法,但是这个题,很明显有:

\[p_i=\max\{a_j+\sqrt{i-j} \}-a_i \]

注意这里是取最大值而不是四边形不等式里取最小值,所以这个东西仍然可以证明满足决策单调性,只需要像我们证明满足四边形不等式就满足取 \(\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]);
}
posted @ 2021-07-04 09:43  hyl天梦  阅读(47)  评论(0编辑  收藏  举报