POJ 3709 K-Anonymous Sequence 斜率DP

K-Anonymous Sequence POJ - 3709

给一个不降的序列,以及一个数 k k k,对这个序列进行操作,允许对某些数减去一定的值,要求操作之后的序列的每种取值的个数至少为 k k k,定义花费为修改前后每个数的绝对值的和,求最小花费。

d p [ i ] dp[i] dp[i] 表示前 i i i 个数的最小花费,则转移方程为:

d p [ i ] = min ⁡ { d p [ j ] + s [ i ] − s [ j ] − a [ j + 1 ] ∗ ( i − j ) } dp[i]=\min\{dp[j]+s[i]-s[j]-a[j+1]*(i-j)\} dp[i]=min{dp[j]+s[i]s[j]a[j+1](ij)}

其中 a [ i ] a[i] a[i] 为序列中第 i i i 个元素的值, s [ i ] s[i] s[i] 为前缀和,这个式子的意思是前 i i i 个数的最小花费为前 j j j 个数的最小花费加上第 j + 1 j+1 j+1 个数到第 i i i 个数都改成 a [ j + 1 ] a[j+1] a[j+1] 的花费。

考虑斜率DP,设 k < j k<j k<j ,且 j j j 优于 k k k,则有:

d p [ j ] + s [ i ] − s [ j ] − a [ j + 1 ] ∗ ( i − j ) < d p [ k ] + s [ i ] − s [ k ] − a [ k + 1 ] ∗ ( i − k ) ( d p [ j ] − s [ j ] + j a [ j + 1 ] ) − ( d p [ k ] − s [ k ] + k a [ k + 1 ] ) < i ( a [ j + 1 ] − a [ k + 1 ] ) y j − y k x j − x k < i \begin{aligned} dp[j]+s[i]-s[j]-a[j+1]*(i-j)&<dp[k]+s[i]-s[k]-a[k+1]*(i-k)\\ (dp[j]-s[j]+ja[j+1])-(dp[k]-s[k]+ka[k+1])&<i(a[j+1]-a[k+1])\\ \frac{y_j-y_k}{x_j-x_k}&<i \end{aligned} dp[j]+s[i]s[j]a[j+1](ij)(dp[j]s[j]+ja[j+1])(dp[k]s[k]+ka[k+1])xjxkyjyk<dp[k]+s[i]s[k]a[k+1](ik)<i(a[j+1]a[k+1])<i

其中 y j = d p [ j ] − s [ j ] + j a [ j + 1 ] , x j = a [ j + 1 ] y_j=dp[j]-s[j]+ja[j+1],x_j=a[j+1] yj=dp[j]s[j]+ja[j+1],xj=a[j+1]

代码如下(注意在队列尾进行判断的时候此处必须是 ≤ \le ):

#include<iostream>
#include<cstdio>
#include<cstring>
//#define WINE
#define INF 0x3f3f3f3f
#define MAXN 500100
using namespace std;
typedef long long ll;
int n,k,T,q[MAXN],h,t;
ll s[MAXN],dp[MAXN],a[MAXN];
ll up(int j,int k){
    return dp[j]-s[j]+j*a[j+1]-(dp[k]-s[k]+k*a[k+1]);
}
ll down(int j,int k){
    return a[j+1]-a[k+1];
}
ll getDP(int k,int i){
    return dp[k]+s[i]-s[k]-a[k+1]*(i-k);
}
int main(){
#ifdef WINE
    freopen("data.in","r",stdin);
#endif
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            s[i]=s[i-1]+a[i];
        }
        h=t=0;q[t++]=0;
        for(int i=k;i<=n;i++){
            while(h+1<t&&up(q[h+1],q[h])<i*down(q[h+1],q[h]))
                h++;
            dp[i]=getDP(q[h],i);
            int j=i-k+1;
            if(j<k)continue;
            while(h+1<t&&up(j,q[t-1])*down(q[t-1],q[t-2])<=up(q[t-1],q[t-2])*down(j,q[t-1]))
                t--;
            q[t++]=j;
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值