codeforces #317B. Minimization dp && greedy

描述:

题目给出一个长为 n 序列A,你可以任意调整序列中数字的顺序。使得

i=1nk|A[i]A[i+k]|
尽可能的小。(序列下标从1开始)

输入:

第一行输入n, k; (2n3105,1kmin(5000,n1))
第二行输入n个数字A[1], A[2], , A[n]。 (109A[i]109)

输出:

输出题目要求的最小解。

提示:

对于样例
3 2
1 2 4
的解是
1
其调整后的序列是
1 4 2

思路

题目要求的可以看成是(n-k)个序对的差的绝对值的和,要让题目所取得值尽可能的小,对于 abcd 有:

|ab|+|cd||ac|+|bd|
因此应该尽可能的然这些序对大小相邻。

对于第 i 个数字的序对 (i, i+k),第 i+k 个数字的序对是 (i+k, i+2*k) ,以此类推后可以得到一个序列B:

i, i+k, i+2k,, i+mk
对于这个序列的绝对之差的和为
A[i+mk]A[i]

而对于一个序列A可以分成多少个A这样的序列B呢?对于大小为13,3的数据可以分成这样几个序列:

12345678910111213

我们可以发现,对于长为n的A序列共需要分成k个序列,其中有 n%k 个序列的长度为 nk+1 而剩下的是k-n%k个长度是 nk
由于我们之前讨论过这些序列的数字必须相邻所以原先A序列的被分成 连续的k 段,并且其中的数量和长度要满足上述要求。

于是问题变成了把一个长度为n的上升序列分成k段,其中有n%k段长度为 nk+1 。要求使得这k段的分别首尾相减的和最小。

dp[i][j] 表示前i段有j段是长度为 (nk+1) 的情况下的前 i 段和的最小值

因此我们只要分第 i 段的长度为 (nk+1) 时的结果和长度为 (nk) 的结果。令 LEN=nk ,可以得到转移:

dp[i][j]=min{dp[i1][j1]+|A[iLEN+j]A[(i1)LEN+(j1)+1]|dp[i1][j]+|A[iLEN+j]A[(i1)LEN+j+1]|

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 300000 + 5;
const int MAXK = 5000 + 5;
int n, k;
long long a[MAXN];
long long dp[MAXK];

long long _get(int l, int r) {
    return a[r] - a[l];
}

int main () {
    scanf ("%d%d", &n, &k);
    for (int i=1; i<=n; i++) {
        scanf ("%I64d", &a[i]);
    }
    sort(a+1, a+1+n);

    int LEN  = n / k;
    int MAXJ = n % k;

    dp[0] = _get(1, 1 * LEN);
    dp[1] = _get(1, 1 * LEN + 1);

    for (int i=2; i<=k; i++) {
        if (MAXJ >= i) {
            dp[i] = dp[i-1] + _get((i-1)*LEN+(i-1)+1, i*LEN+i);
        }
        for (int j=min(MAXJ, i); j>=1; j--) {
            if (j == i) continue;
            dp[j] = min(dp[j-1] + _get((i-1)*LEN+(j-1)+1, i*LEN+j)  , 
                        dp[j] + _get((i-1)*LEN+j+1, i*LEN+j));
        }
        dp[0] += _get((i-1)*LEN+1, i*LEN);
    }

    printf ("%I64d\n", dp[MAXJ]);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值