NKOJ P1048 筷子

Description

A先生有很多双筷子。确切的说应该是很多根,因为筷子的长度不一,很难判断出哪两根是一双的。这天,A先生家里来了 K K 个客人,A先生留下他们吃晚饭。加上A先生,A夫人和他们的孩子小A,共 K + 3 K+3 个人。每人需要用一双筷子。A先生只好清理了一下筷子,共 N N 根,长度为 T 1 , T 2 , T 3 , , T N T_1,T_2,T_3,……,T_N .现在他想用这些筷子组合成 K + 3 K+3 双,使每双的筷子长度差的平方和最小。(怎么不是和最小??这要去问A先生了,呵呵)

Input Format

输入文件共有两行,第一行为两个用空格隔开的整数,表示 N , K ( 1 N 100 , 0 < K < 50 ) N,K(1≤N≤100, 0<K<50)
第二行共有N个用空格隔开的整数,为 T i T_i .每个整数为1~50之间的数。

Output Format

输出文件仅一行。如果凑不齐K+3双,输出-1,否则输出长度差平方和的最小值。

Sample Input

10 1
1 1 2 3 3 3 4 6 10 20

Sample Output

5

Hint

第一双 1 1 1 1
第二双 2 2 3 3
第三双 3 3 3 3
第四双 4 4 6 6
( 1 1 ) 2 + ( 2 3 ) 2 + ( 3 3 ) 2 + ( 4 6 ) 2 = 5 (1-1)^2+(2-3)^2+(3-3)^2+(4-6)^2=5

分析:

这道题看到就知道没有别的办法了,只能DP,贪心选择显然有问题。

排序是必须的,难点在于状态定义,很容易想到的是令 f i f_i 为从 1 1 i i 选择 K + 3 K + 3 双筷子的最小平方差之和。

可是这样明显转移困难,考虑加一个参数。

f i , j f_{i,j} 为区间 i i j j 内选择 K + 3 K + 3 双筷子的代价(平方差之和)。

显然这样转移也比较困难,当 j i > K + 3 j-i > K + 3 时根本无法转移(除非你想每个测试点都TLE)。

f i f_i 表示选择 i i 双筷子最小的代价?

还是没法转移……

扫描二维码关注公众号,回复: 11605718 查看本文章

于是我就卡壳了……(原谅本蒟蒻水平有限)。

然后去问老师,老师开始把状态定义成第三种状态,也是没法转移,于是老师加了个限制:

f i , j f_{i,j} 表示前 i i 个数中取 j j 双筷子最小的代价

于是状态变得有序化了,我就自己推导出了决策与方程。

决策:

对于每个 f i , j f_{i,j} ,决策不外乎就是让 a i a_i a i 1 a_{i-1} 作为一双筷子和放弃 a i a_i 双筷子。

方程:

d p i , j = m i n ( d p i 1 , j , d p i 2 , j 1 + ( a i a i 1 ) 2 ) dp_{i,j} = min(dp_{i-1,j},dp_{i-2,j-1} + (a_i-a_{i-1})^2)

阶段:

以前 i i 个数作为一个阶段。

综上,时间复杂度为 O ( N 2 ) O(N^2) ,所以 100 100 的数据明显放水

C o d e Code

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

int a[105], dp[105][105], n, k;

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i ++)
	scanf("%d", a + i);
	sort(a + 1, a + n + 1);//别忘了排序
	k += 3;
	if (k > (n >> 1))//如果根本不够分K+3双,输出-1
	{
		puts("-1");
		return 0;
	}
	for (int i = 1; i <= 51; i ++)//初始化QAQ
	for (int j = 0; j < 2 * i; j ++)//如果该区间内不够分i双筷子,那么初始化为INF
	dp[j][i] = 0x3fffffff;//别整7f,一不小心就炸了
	for (int i = 2; i <= n; i ++)
	for (int j = 1; j <= (i >> 1); j ++)//最多只能分i/2双筷子
	{
		dp[i][j] = dp[i - 2][j - 1] + (a[i] - a[i - 1]) * (a[i] - a[i - 1]);//方程,如果取a[i]与a[i-1]作为一双筷子
		if (dp[i - 1][j] < dp[i][j]) dp[i][j] = dp[i - 1][j];//如果放弃第i根筷子,那么取dp[i-1][j]
	}
	printf("%d", dp[n][k]);
	return 0;//其实代码不长,主要是状态定义(这是一道区间DP)
}

猜你喜欢

转载自blog.csdn.net/jvruo_shabi/article/details/108266705