斜率优化dp专题学习

推荐大牛博客:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html

我就不一一赘述了,反而会推的莫名其妙。

就说一点把,斜率优化dp基本上都是可以化到 dp[i]=min(dp[j]+cost(j+1,i)) 这样的形式,二维的就是dp[i][m]=min(dp[j][m-1]+cost(j+1,i));

然后每次假设k<j<i,然后假设j比k更优,就能得到一个式子,化简得到 (yj-yk)/(xj-xk) < f[i],这样的形式,令g(j,k)=(yj-yk)/(xj-xk) , g(j,k) < f[i],表示j比k更优

所以每次通过化简得到的式子之后,我们可以用单调队列来O(n)(二维是O(nm))的维护最优解,第一个循环应该是在单调队列的头上找最优解点,如果g(head+1,head)<f[i],表示head+1比head更优,所以head++,直到不成立,然后求出dp[i].

之后是维护单调队列,把i放进去,i对以后答案产生的影响,就根据g(i,tail-1) 和g(tail-1,tail-2)来比较,如果g(i,tail-1) < g(tail-1, tail-2) ,对于以后一个解dp[x],如果g(tail-1,tail-2) < dp[x],则g(i,tail-1) < dp[x],表明tail-1比tail-2更优,但是i比tail-1更优,所以tail--,直到不成立,把i放进去即可

主要实现的过程就是这样,二维的话也同理,在求g(j,k)的时候需要用到上一轮求出来的结果呗。

如果是套路题,那么主要的难点就在于如何O(1)的求解cost了


题目解析:

hdu 3507

http://acm.hdu.edu.cn/showproblem.php?pid=3507

斜率优化基础题:cost(j+1,i)=(sum[i]-sum[j])^2+M,其余完全符合上述条件,直接套路过

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           500005
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

LL sum[MAX];
int q[MAX];
int n,m;
LL dp[MAX];
LL getup(int i,int j){
    return dp[i]+sum[i]*sum[i]-dp[j]-sum[j]*sum[j];
}

LL getdown(int i,int j){
    return 2*(sum[i]-sum[j]);
}

LL getdp(int i,int j){
    return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
}
int main(){
    //freopen("in.txt","r",stdin);
    while(cin>>n>>m){
        sum[0]=0;
        for(int i=1;i<=n;i++){
            int a;
            scanf("%d",&a);
            sum[i]=sum[i-1]+a;
        }
        int head=0,tail=0;
        q[tail++]=0;
        mem(dp,0);
        for(int i=1;i<=n;i++){
            while(tail>head+1&&getup(q[head+1],q[head])<=sum[i]*getdown(q[head+1],q[head])) head++;
            dp[i]=getdp(i,q[head]);
            while(tail>head+1&&getup(i,q[tail-1])*getdown(q[tail-1],q[tail-2])<=getup(q[tail-1],q[tail-2])*getdown(i,q[tail-1])) tail--;
            q[tail++]=i;
        }
        cout<<dp[n]<<endl;
    }
    return 0;
}


hdu 2829

http://acm.hdu.edu.cn/showproblem.php?pid=2829

也是给你一段n个数字,然后让你切m刀,分成m+1段,每段的cost(j+1,i)=((sum[i]-sum[j])^2-(sum[i]^2-sum[j]^2))/2;

通过记录两个数组,即可O(1)的求cost,然后二维的斜率优化,和一维其实是一样的,只要在求斜率的时候多传进去一个这是第j次,然后用j-1次的数据即可

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           1005
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

LL sum[MAX];
LL sum2[MAX];
LL dp[MAX][MAX];
int q[MAX];

LL getup(int j,int k,int x){
    return dp[j][x-1]+(sum[j]*sum[j]+sum2[j])/2-dp[k][x-1]-(sum[k]*sum[k]+sum2[k])/2;
}

LL getdown(int j,int k,int x){
    return sum[j]-sum[k];
}

LL getdp(int i,int j,int x){
    return dp[j][x-1]+((sum[i]-sum[j])*(sum[i]-sum[j])-(sum2[i]-sum2[j]))/2;
}

int main(){
    //freopen("in.txt","r",stdin);
    int n,m;
    while(cin>>n>>m){
        if(n==0&&m==0) break;
        sum[0]=sum2[0]=0;
        for(int i=1;i<=n;i++){
            LL a;
            scanf("%I64d",&a);
            sum[i]=sum[i-1]+a;
            sum2[i]=sum2[i-1]+a*a;
        }
        mem(dp,INF);
        dp[0][0]=0;
        for(int j=1;j<=m+1;j++){
            int head=0,tail=0;
            q[tail++]=0;
            for(int i=1;i<=n;i++){
                while(tail>head+1&&getup(q[head+1],q[head],j)<=sum[i]*getdown(q[head+1],q[head],j)) head++;
                dp[i][j]=getdp(i,q[head],j);
                while(tail>head+1&&getup(i,q[tail-1],j)*getdown(q[tail-1],q[tail-2],j)<=getup(q[tail-1],q[tail-2],j)*getdown(i,q[tail-1],j)) tail--;
                q[tail++]=i;
            }
        }
        cout<<dp[n][m+1]<<endl;
    }
    return 0;
}


poj 2018

http://poj.org/problem?id=2018

给你一段长度n的数,然后找出连续长度大于等于m的段里面最大的平均值

这题乍一看,很难和斜率优化的模型套上关系啊,但是仔细看呢,其实一段的平均值,(sum[i]-sum[j])/(i-j),这不就是一段斜率么

然后因为长度要大于等于m,所以i直接从m开始计数,把i-m和单调队列中的点进行维护

如何维护呢,首先i-m先要放进去,因为这个点最后也有可能作为最优解的,放的过程和前面一样,直接找斜率,如果g(i-m,tail-1)<g(tail-1,tail-2),那么tail--

然后放进去之后呢,这是一个下凸壳的形状,假设现在考察i点,下凸壳上的点都有可能作为最优解,为啥刚才舍去的点不可能呢,可以画图来看,如果是上凸壳,对于后面的点,i必然比tail-1更优,所以可以舍去上凸壳,只保留下凸壳,然后下凸壳上如果求解呢,通过画图可知,下凸壳上的点,有个临界值,临界值之前,后面的点比前面的点更优,临界值之后,前面的点比后面的点更优,所以需要在凸壳上二分,寻找最优的临界点j,这个临界点就是dp[i]的最优解,dp[i]=(sum[i]-sum[j])/(i-j);

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX           100005
#define   MAXN          1000005
#define   maxnode       10
#define   sigma_size    2
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   lrt           rt<<1
#define   rrt           rt<<1|1
#define   middle        int m=(r+l)>>1
#define   LL            long long
#define   ull           unsigned long long
#define   mem(x,v)      memset(x,v,sizeof(x))
#define   lowbit(x)     (x&-x)
#define   pii           pair<int,int>
#define   bits(a)       __builtin_popcount(a)
#define   mk            make_pair
#define   limit         10000

//const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const LL     INFF  = 0x3f3f;
const double pi    = acos(-1.0);
const double inf   = 1e18;
const double eps   = 1e-9;
const LL     mod   = 1e9+7;
const ull    mxx   = 1333331;

/*****************************************************/
inline void RI(int &x) {
      char c;
      while((c=getchar())<'0' || c>'9');
      x=c-'0';
      while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/

double sum[MAX];
int q[MAX];

double getup(int j,int k){
    return sum[j]-sum[k];
}

double getdown(int j,int k){
    return j-k;
}

int main(){
    //freopen("in.txt","r",stdin);
    int n,m;
    cin>>n>>m;
    sum[0]=0;
    for(int i=1;i<=n;i++){
        int a;
        RI(a);
        sum[i]=sum[i-1]+a;
    }
    int top=0;
    //q[top++]=0;
    double ans=0;
    for(int i=m;i<=n;i++){
        int j=i-m;
        while(top>1&&getup(j,q[top-1])*getdown(q[top-1],q[top-2])<=getup(q[top-1],q[top-2])*getdown(j,q[top-1])) top--;
        q[top++]=j;
        int l=0,r=top-2;
        while(l<=r){
            int mid=(l+r)/2;
            if(getup(i,q[mid])*getdown(i,q[mid+1])<=getup(i,q[mid+1])*getdown(i,q[mid])) l=mid+1;
            else r=mid-1;
        }
        ans=max(ans,getup(i,q[l])/getdown(i,q[l]));
    }
    int ret=ans*1000;
    printf("%d\n",ret);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值