题目链接: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的子序列
因为要求重排,所以直接排序后,题目就转换为:
扫描二维码关注公众号,回复:
11436904 查看本文章
选出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
**/