多阶段决策dp。(另一种思考方式是转化为求 最小的不相邻的要求必须正好选k
个的子序列和。。代码一样。。参考这个的第二种方法)
选k
对物品,要求每对物品重量的平方差的和最小。
这道题必须想清楚一个结论:最优解的每一对物品在排序后的序列中一定都是相邻的。这句话从下面两个方面理解:
- 每一对物品相邻 指的是这对物品内的两个物品是相邻的。
- 最优解的每一对物品都相邻。所以,若
n=2k
,则最优解可以排序后直接得出(因为只有一种构造方式)。
为什么?若某一对物品不相邻,假设中间有其他对物品干扰了,则只有两种情况,而这两种情况都可以转化为更优的方案,即没有重叠计算差值。(下图数轴表示已排序的物品序列)
dp[i][j]
表示可选取物品的范围为前i
个,选取j
对的子问题的最优解。
逐个扩大子问题范围,递推求解。在处理前i
个时,前面的子问题都已计算,当前只多了一个可选择的物品i
,那么只用考虑选或不选物品i
。若选,则只能将物品i
和物品i-1
配对,由子问题i-2
转移。
注意只把初始触发状态设为0
(或者加上边界状态(也可以显式排除):所有范围下都选0
对)。
虽然是求最小,但如果全设为0
也AC,证明了没有引用非法状态。但要是把if(i == 2*j)
去掉就WA了(这样的话必须先全设为INF
,因为引用了非法状态)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std; // 可以证明,必须按照从小到大,每一对都是相邻的两个物品,才可能有最小值
int N, K;
int w[2000];
int dp[2000][1000]; // 前i物品选j对的最小值
void init()
{
for (int i = 0; i <= N; i++)
dp[i][0] = 0; // 初始的合法状态
}
int main()
{
for (; ~scanf("%d%d", &N, &K);)
{
init();
for (int i = 1; i <= N; i++)
scanf("%d", &w[i]);
sort(w + 1, w + N + 1);
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= i / 2; j++)
{
if (i == 2 * j)
dp[i][j] = dp[i - 2][j - 1] + (w[i - 1] - w[i])*(w[i - 1] - w[i]);
else
dp[i][j] = min(dp[i - 1][j], dp[i - 2][j - 1] + (w[i - 1] - w[i])*(w[i - 1] - w[i])); // 不选与选
}
}
printf("%d\n", dp[N][K]);
}
return 0;
}