BZOJ2216: [Poi2011]Lightning Conductor

BZOJ2216: [Poi2011]Lightning Conductor

Dp·决策单调性

题解:

pi=max{aj|ij|}ai

只想出一个 O(nnlogn) 的暴力QwQ
我们发现对于一个i, |ij| 的上取整是分段的,这样的段有 O(n) 个,因此枚举一下,线段树查询这个区间最值,更新答案即可。

然后hzw大爷用一个 O(nn) 的做法狂虐本蒟蒻QwQ而且很好写QwQ

就是把线段树查询换成滑动窗口维护。因为段的长度是固定的,就相当于滑动窗口维护一个定长区间的最值,然后用它直接更新相应的 pi .

注意两处//take care!。滑动窗口要完全滑出去,才能更新所有左边要更新的f[].

Code:

#include <iostream>
#include <cstring>
#include <cstdio>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
const int N = 200005;

int f[N],n,a[N];

struct Queue{
    int d[N]; int head,tail;
    void clear(){ head=tail=0; }
    void autopop(int lim){ while(head<tail && d[head]<=lim)head++; }
    void push(int i){ while(head<tail && a[d[tail-1]]<=a[i])tail--; d[tail++]=i; }
    int front(){ return d[head]; }  bool empty(){ return head==tail; }
} q;

void work(int len,int lp,int rp,int d){
//  D(len); D(lp); D(rp); D(d); E;
    q.clear();
    for(int i=1;i<=n+len;i++){ //take care!
//      D(i); E;
        q.push(i); q.autopop(i-len);
        int mx=a[q.front()]+d; 
//      D(q.front()); D(mx); E;
//      D(i-lp); D(i+rp); E;
        if(i-lp>0) f[i-lp]=max(f[i-lp],mx);
        if(i+rp<=n)f[i+rp]=max(f[i+rp],mx);
    }
}

int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",a+i);
    int lp,rp;
    for(int i=1;i*i<=(n<<1);i++){ //take care!
        lp=i*i; rp=(i-1)*(i-1)+1; work(i*i-(i-1)*(i-1),lp,rp,i);
    }
    for(int i=1;i<=n;i++)printf("%d\n",max(0,f[i]-a[i]));
}

正解:决策单调性!

假如k

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
const int N = 1000005;

int n;
int a[N], ans[N];
double f1[N], f2[N];

struct Data{
    int id,l,r;
    Data(){}
    Data(int _id,int _l,int _r){ id=_id; l=_l; r=_r; }
} q[N];

double cal(int j,int i){
    return a[j]-a[i]+sqrt(i-j);
}

void dp(double f[]){
    Data *head=q, *tail=q;
    *tail=Data(1,2,n); tail++;
    for(int i=2;i<=n;i++){
        while(head<tail && head->r < i) head++;
        f[i]=cal(head->id, i);
        head->l = i;
        while(head<tail && head->l > head->r) head++;
        while(head<tail && cal((tail-1)->id,(tail-1)->l) < cal(i,(tail-1)->l)) tail--;
        if(head<tail){
            int l=(tail-1)->l, r=(tail-1)->r, mid;
            while(l<=r){
                mid=(l+r)>>1;
                if(cal((tail-1)->id,mid) < cal(i,mid)) r=mid-1;
                else l=mid+1;
            }
            ++r;
            if(r<=n){
                (tail-1)->r = r-1;
                *tail=Data(i,r,n); tail++;
            }
        }
        else{
            *tail=Data(i,i+1,n); tail++;
        }
    }
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    dp(f1);
    reverse(a+1,a+1+n);
    dp(f2);
//  for(int i=1;i<=n;i++) D(f1[i]); E;
//  for(int i=n;i>=1;i--) D(f2[i]); E;
    for(int i=1;i<=n;i++) ans[i]=ceil(max(f1[i],f2[n-i+1]));
//  for(int i=1;i<=n;i++) D(ans[i]), E;
    for(int i=1;i<=n;i++) printf("%d\n",max(ans[i],0));
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值