題目鏈接:https://codeforces.ml/contest/571/problem/B
題目大意:
給一段序列a_i,你可以對其重排,要求重排之後的:
最大
題目思路:
首先裂項相消:
a[i+k]-a[i]
a[i+2*k] - a[i+k]
sum = a[i+x*k]-a[i]
性質1:也就是這個每個數相距爲k的子序列的權值實際即爲a[i+x*k]-a[i]
性質2:
可以發現有長度爲n%k個 n/k+1的子序列
有k-n%k個 n/k的子序列
因爲要求重排,所以直接排序後,題目就轉換爲:
選出n%k段連續區間長度爲n/k+1的區間 和 k-n%k個區間長度爲n/k的區間,使得每段區間的最大值減最小值的和最小
所以令dp[i][k] 選了i段n/k+1的區間,k段n/k的區間
所以轉移方程:
令aim = i*(x+1) + k*x ,x = n/k
這種dp的狀態轉移方程並不常見,正確性是因爲選了i段與k段可以把當前選擇了多少個數給確定出來
這和之前牛客多校有一個狀態轉移非常的相似,不得不說還是要留心這種轉移
Code:
/*** keep hungry and calm CoolGuang!***/
#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=2e18;
const int maxn=1e6+6;
const int mod=1e9+7;
const double eps=1e-15;
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll dp[5005][5005];
ll num[maxn];
int main()
{
read(n);read(m);
ll x = n/m;
ll len1 = n%m;///x+1
ll len2 = m-n%m;///x
for(int i=1;i<=n;i++) read(num[i]);
sort(num+1,num+1+n);
for(int i=0;i<=len1;i++){
for(int k=0;k<=len2;k++){
if(!i&&!k){
dp[i][k] = 0;
continue;
}
dp[i][k] = INF;
int aim = i*(x+1)+k*x;
if(i) dp[i][k] =min(dp[i][k],dp[i-1][k]+num[aim]-num[aim-x]);
if(k) dp[i][k] =min(dp[i][k],dp[i][k-1]+num[aim]-num[aim-x+1]);
}
}
printf("%lld\n",dp[len1][len2]);
return 0;
}
/**
5 2 2
1 5
1 2
2 3
3 4
4 5
**/