HN2015集训 永远亭的竹笋采摘

第一反应肯定是dp啦,然后就会愉快的T成一头象拔蚌。
那么,说说正解吧。
显然,选取的区间一定可以是两头为差值最小的。那就好办了,只需要预处理出所有的这样的区间,再dp即可,由于数据是随机生成的,所以这样的区间不会太多,有人验证了,好像是O(n)级别的。
预处理怎么搞?分块优化,pre[i][j]表示在第i块内的数,与j差值的最小值。每一块内的数取出来排序,两个下标跟着动就行了。
然后把那些区间搞出来,从a[n]到a[1],先暴力处理自身所在的块内的。然后在看后面的块,如果最小值更有,那就再暴力处理这个块。得出的区间要注意,如果有区间被完全包含并且最小值还小于这个区间,那么这个区间显然可以舍去,最小值可以用树状数组来维护。
把右端点相同的区间链起来,一次非常简单的dp就得出答案了。

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
//#include<ctime>
#define N 50005
#define ll long long
using namespace std;
const int block=210;
const int inf=0x3f3f3f3f;
template <typename T>
inline void _read(T& x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9'){if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,m,tot,top;
int a[N],Pre[255][N],q[N],f[N][1005];
struct node{
    int l,r,d;
    node(){}
    node(int L,int R,int D){l=L;r=R;d=D;}
};
vector<node> tra[N];
int c[N];
int lowbit(int x){return x&(-x);}
void modify(int x,int d){
    for(int i=x;i<=50000;i+=lowbit(i)){
        c[i]=min(c[i],d);
    }
}
int getmin(int x){
    int temp=inf;
    for(int i=x;i;i-=lowbit(i)){
        temp=min(temp,c[i]);
    }
    return temp;
}
bool check(int x){return (x>=1&&x<=n);}
void pre(){
    int i,j,k,l,r,p1,p2;
    for(i=1;i<=tot;i++){
        top=0;
        l=(i-1)*block+1;r=min(n,i*block);
        for(j=l;j<=r;j++)q[++top]=a[j];
        sort(q+1,q+1+top);
        p1=p2=1;
        for(j=1;j<=n;j++){
            Pre[i][j]=inf;
            while(p1<top&&q[p1+1]<j)p1++;
            while(p2<top&&q[p2]<=j)p2++;
            if(check(p1)&&q[p1]<j)Pre[i][j]=min(Pre[i][j],j-q[p1]);
            if(check(p2)&&q[p2]>j)Pre[i][j]=min(Pre[i][j],q[p2]-j);
        }
    }
    /*for(i=1;i<=tot;i++){
        for(j=1;j<=10;j++){
            cout<<Pre[i][j]<<" ";
        }
        cout<<endl;
    }*/
}
int temp;
void work(int x,int st,int num){
    int i,j,k,r;
    r=min(n,num*block);
    for(i=st;i<=r;i++){
        int t=abs(a[i]-a[x]);
        if(t&&t<temp){
            temp=t;
            int p=getmin(i);
            if(temp<p){
                modify(i,temp);
                tra[i].push_back(node(x,i,temp));
            }
        } 
    }
}
int main(){
    int i,j,k,st;
    cin>>n>>m;
    tot=(n-1)/block+1;
    for(i=1;i<=n;i++)_read(a[i]);
    for(i=0;i<=50000;i++)c[i]=inf;
    pre();
    top=0;
    for(i=n;i;i--){
        temp=inf;
        int bel=(i-1)/block+1;
        work(i,i+1,bel);
        for(j=bel+1;j<=tot;j++){
            if(Pre[j][a[i]]<temp){
                work(i,(j-1)*block+1,j);
            }
        }
    }
    for(i=0;i<=n;i++){
        for(j=0;j<=m;j++){
            f[i][j]=inf;
        }
    }
    f[0][0]=0;
    int ans=inf;
    for(i=1;i<=n;i++){
        f[i][0]=0;
        for(j=1;j<=m;j++){
            f[i][j]=min(f[i][j],f[i-1][j]);
            for(k=0;k<tra[i].size();k++){
                f[i][j]=min(f[i][j],f[tra[i][k].l-1][j-1]+tra[i][k].d);
            }
        }
    }
    cout<<f[n][m];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值