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]);
}
}