斜率dp

第二道斜率dp问题,不会做。。。。。
真是太狼狈了!!!但是也加深了对斜率dp的理解

这一题,我上来就用动态规划求出了任意两仓库之间所产生的贡献度,很快啊!!!
假设我们用f[i][j]来表示从仓库i到仓库j连续的这一段所产生的贡献度。
f[i][j] = f[i][j-1]+(sum[j-1]-sum[i-1])*a[j]
但是,当我进行dp时却傻眼了!!!
我的状态定义为:dp[i][j]
指到i为止,使用了j个***答案。
而dp转移方程就是dp[i][j] = min(dp[k][j-1]+f[k+1][i])
我怎么可能对这个式子进行斜率dp啊
dp[i][j] = dp[k][j-1]+f[k+1][i]

因为我的状态定义是二维的,所以,我是一定要把一维看作是常数的。
但是,我们却发现在这个式子中,k与两个维数都想关联了。
也就是说,这个式子中我们把谁看作是斜率,把谁看作是点呢?
若我们把维数i放在外面,那么我们就可以把i看作是常数。
那么在更新j这一维数时,是不用在意的。
那么我们想想,什么是斜率?dp[k][j-1]?怎么可能有k
那么斜率是个定值?1?
那么点就是(dp[k][j-1],-f[k+1][i])?
看似可以,实则放屁。
注意力哈,我们发现这个点中有j,而j正式我们更新的维度!
这就很糟糕了!!在我们维护的这一凸包中,点竟然是不断变化的!很明显不可能!
于是,我不会做了。

那么我究竟是在哪一步出错了呢?
状态的定义并没有错,但是状态转移方程写错了。
我们应该写一个更好的状态转移方程,能够使用斜率dp的状态转移方程。
dp[i][j] = dp[k][j-1] + f[k+1][i]
坏事就坏事在f[k+1][i] 他把k和i直接建立关联了。
我们不能这样求
不妨这样求,f[i]为1到i这一连续区间的贡献度,
然后我们就知道了上面,k+1到i区间的贡献度其实是可以这样算的
f[i]-f[k]-sum[k](sum[i]-sum[k])
然后我们的状态转移方程就是
dp[i][j] = dp[k][j-1] + f[i]-f[k]+sum[k]
(sum[i]-sum[k])
这个方程是符合斜率优化的性质的!!!!!!!
真是太巧妙了!!!
我们在这里把j看作是常数,把i放在内循环,为什么这样呢?
因为dp[k][j-1]我们可以看到k是和j绑定在一起的,所以如果优先更新j的话
那么就会出“动点”问题!!!
所以优先更新i
而这是一个二维dp,我们的斜率优化其实是作用在一个维度上的,所以对于每一个j我们都要有一个队列去优化!!!
另外这里i,j,k其实还是有范围的!!一定要注意范围!并且dp也是有初始态的,
另外在队列中放置第一个元素也要好好斟酌!!!!

#include<iostream>
#include<algorithm>
#include<deque>
using namespace std;
typedef long long ll;
int n,m;
ll f[1100];
ll dp[1100][1100];
ll a[1100];
ll sum[1100];
deque<int> que;
bool check(int j,ll k){
    int a = que[0],b=que[1];
    ll x1 = sum[a],y1 = dp[a][j-1]-f[a]+sum[a]*sum[a];
    ll x2 = sum[b],y2 = dp[b][j-1]-f[b]+sum[b]*sum[b];
    if (x1<x2)swap(x1,x2),swap(y1,y2);
    return y1-y2<=k*(x1-x2);
}
bool check2(int j,int i){
    int a=que[que.size()-1],b=que[que.size()-2];
    ll x1 = sum[a],y1 = dp[a][j-1]-f[a]+sum[a]*sum[a];
    ll x2 = sum[b],y2 = dp[b][j-1]-f[b]+sum[b]*sum[b];
    ll x3 = sum[i],y3 = dp[i][j-1]-f[i]+sum[i]*sum[i];
    if ((x3-x2)*(x1-x2)<0)swap(x1,x2),swap(y1,y2);
    return (x1-x2)*(y3-y2)<=(x3-x2)*(y1-y2);
}
int main(){
    while (scanf("%d %d",&n,&m)){
        if (n==0&&m==0)break;
        for (int i=1;i<=n;++i)scanf("%lld",&a[i]);
        for (int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
        for (int i=1;i<=n;++i)f[i] = f[i-1]+sum[i-1]*a[i];
        for (int i=1;i<=n;++i)dp[i][0] = f[i];
        for (int j=1;j<=m;++j){
            que.clear();que.push_back(j);
            for (int i=j+1;i<=n;++i){
                ll k = sum[i];
                while (!que.empty()&&que.front()<j)que.pop_front();
                while (que.size()>=2&&check(j,k))que.pop_front();
                int o = que.front();
                dp[i][j] = dp[o][j-1]+f[i]-f[o]-sum[o]*(sum[i]-sum[o]);
                while (que.size()>=2&&check2(j,i))que.pop_back();
                que.push_back(i);
            }
        }
        printf("%lld\n",dp[n][m]);
    }
}

在写出斜率dp的方程时,我们一定要保证斜率dp中至少有一维与我们目前选择的点不绑定!!