#317 (div.2) D. Minimization

1.题目描述:点击打开链接

2.解题思路:本题利用dp解决。不过首先要做一点分析。根据题意,我们需要在数组中,每相邻k个数要进行一次求和运算,那么,我们不妨把从下标i开始计算的数全部找出来,把他们看做一组,即下标为i,i+k,i+2k,...i+(n-1-i)/k*k这些数看做一个组的,我们发现,最多只能有k组(i只能从0取到k-1,再往后就会出现重复计算)。而且,只有n%k个组是包含n/k+1个数,k-n%k个组是包含n/k个数,我们把具有n/k+1个数的组叫“大组”,有n/k个数的组叫“小组”,这样,问题就转化为如何挑选一些大组(剩下的都是小组),使得最终的ans最小。


那么,首先考虑如何才能让一个组的结果对答案的贡献尽量小。由于组内的数都是原数组中每隔k个数取的,因此,对答案的贡献就是sum{Ai-Ai+1|i from 0 to m-1,m是该组的元素个数}。可以发现,只有当组内元素都从小到大排列之后,该组的贡献值最小,等于abs(Am-1-A0),这就告诉我们,首先要对原数组进行排序。我们定义状态d(i,j)表示一共有i个组,其中j个为大组时的答案,那么不难得到如下的状态转移方程:

d(i+1,j+1)=min(d(i+1,j+1),d(i,j)+a[q+s]-a[q]);

d(i+1,j)=min(d(i+1,j),d(i,j)+a[q+s-1]-a[q]);

其中,s=n/k, q=i*s+j(因为j*(s+1)+(i-j)*s=i*s+j,表示下一个组的起始下标),那么如果作为大组处理,最后一个元素就是a[q+s],作为小组处理,就是a[q+s-1]。这样,最终的答案就是d(k,r)(r=n%k)。

3.代码:

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

const int N=5000+10;
const ll INF=1e15;

int a[1000000];
ll d[N][N];

int main()
{
    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        rep(i,n)
        scanf("%d",&a[i]);
        sort(a,a+n);

        int s=n/k;//小组的个数
        int r=n%k;//大组的最大组数

        rep(i,k+1)
            rep(j,k+1)
                d[i][j]=INF;  //初始化为INF
        d[0][0]=0;
        rep(i,k)
            rep(j,k)
        {
            if(d[i][j]==INF)continue;
            int q=i*s+j;  //下一个组的起始下标
            if(q+s<n)
                d[i+1][j+1]=min(d[i+1][j+1],d[i][j]+a[q+s]-a[q]);//把下一个组当做大组处理
            if(q+s-1<n)
                d[i+1][j]=min(d[i+1][j],d[i][j]+a[q+s-1]-a[q]);//把下一个组当做小组处理
        }
        printf("%d\n",d[k][r]);
    }
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值