POJ 3709 K-Anonymous Sequence

本文讨论了一个关于序列分组的优化DP算法问题,目标是将升序序列分组,使得每组中元素数量不少于指定值,并计算各组内元素与最小元素之差的和,最终求出总和最小的方案。文中详细阐述了连续取值相较于离散取值的优势,并通过DP方程解决了此问题。特别地,文章还强调了k值限制条件下的队列管理策略,确保每组元素数量满足要求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

斜率优化dp
题意:
将题目转化下:将一个升序的,有N个元素的序列,分组。要求每组的元素不少于K个,计算出组内各元素与最小元素的之差的和,将每组的这个值加起来,其和要最小。

很容易可以得出一个结论:连续取比离散取得到的结果要好(很容易证,所以不证)。

由以上可得DP方程:
dp[i]=MIN(dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]); j<i-k+1

f[i] =   min{f[j] +j *x[j+1] -sum[j] -i *x[j+1]}  +sum[i] (转化后的)

这道题需要注意的就是有k的限制,因此需要延迟入队~
就是说走完 i 这个点的时候,把下一个点即(i+1)可能用到的状态加入队列,就是i-k+1
但具体的入队时间呢,
若延迟K-1个回合加入,有可能使前一组的个数少于K个。
从开始算起,如果延迟一个回合加入了,那么利用f[1到k]跟新了f[i],而f[1到k]并不是满足数量大于等于k~~
若延迟2*k-1个回合加入,则不会出现这情况。但此时加入的数应是i-k+1(假设是第I回合),因为第1到k个的f值实在是不能用,而第k之后的是通过f[0]更新的,相当于第一个分组大于等于k个,然后后面的就可以用这个状态开始更新了~~
要更新下一个的时候才加入i-k+1,这样能保证即使队列弹到空也能保证间隔的数目是大于等于k的,而且只有当前面的解没后面的解优时才会弹出,所以此时的队尾值必定是最优的~

具体判断斜率大小关系的时候应该这样,因为要弹队列尾,那么就一定是后加入的要优于队尾的,然后推出一个大小关系,顺延到前面,证明一下合理性就ok了,注意过程中符号的变换~

#include<bits/stdc++.h>
using namespace std;
const int MAXX = 500010;
int n, k, queue[MAXX];
__int64 sum[MAXX], f[MAXX], a[MAXX];
__int64 dy(int j1, int j2)
{
    return  (f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]*j2);
}
__int64 dx(int j1, int j2)
{
    return  (a[j1+1] - a[j2+1]);
}
void dp()
{
    int i, j, head, tail, x, y, z;
    head = tail = 0;
    queue[0] = 0;
    for(i = 1; i <= n; i++)
    {
        while(head<tail && dy(queue[head], queue[head+1])>=i*dx(queue[head], queue[head+1]))
            head++;
        j = queue[head];
        f[i] = f[j] + sum[i] - sum[j] - a[j+1]*(i-j);
        if(i >= 2*k-1)    //实际上是i-k+1>=k
        {
            z = i-k+1;
            while(head < tail)
            {
                x = queue[tail-1];
                y = queue[tail];
                if(dy(x,y)*dx(y,z) >= dy(y,z)*dx(x,y))  tail--;
                else  break;
            }
            queue[++tail] = z;
        }
    }
}
int main()
{
    int t, i;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &k);
        sum[0] = 0;
        for(i = 1; i <= n; i++)
        {
            scanf("%I64d", a+i);
            sum[i] = sum[i-1] + a[i];
        }
        dp();
        printf("%I64d\n", f[n]);
    }
    return 0;
}
#include <iostream>  
#include <cstdio>  
#include <string.h>  
#include <climits>  
#include <algorithm>  
#include <deque>
using namespace std; 
#define ll long long
const int maxn=5e5+10;
ll sum[maxn],f[maxn],a[maxn];
int t ,n,k;
ll dy(int i,int j){
    return (f[i]-sum[i]+i*a[i+1])-(f[j]-sum[j]+j*a[j+1]);
}
ll dx(int i,int j){
    return (a[i+1]-a[j+1]);
}
int main(){
    cin>>t;
    while(t--){
        deque<int>q;
        scanf("%d%d",&n,&k);memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        q.push_back(0);
        for(int i=1;i<=n;i++){
            while(q.size()>=2&&(dy(q[0],q[1])>=i*(dx(q[0],q[1])))){
                q.pop_front();
            }
            int now=q.front();
            f[i]=f[now]+sum[i]-sum[now]-a[now+1]*(i-now);
            if(i-k+1>=k){
                int z=i-k+1;
                while(q.size()>=2){
                    int x=q[q.size()-2];int y=q[q.size()-1];
                    if(dy(x,y)*dx(y,z)>=dy(y,z)*dx(x,y)) q.pop_back();
                    else break;
                }
                q.push_back(i-k+1);
            }           
        }
        printf("%lld\n",f[n]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值