今日忙于学前端,(雾)反正只写了一道题。
对dp又有了新的理解:寻找状态转移方程的关键在于对问题的分类,也就是发现重叠子问题的过程。
hdu 1421-搬寝室 (排序+dp)
(题目我还是贴一下吧)
搬寝室
搬寝室是很累的,xhd深有体会.时间追述2006年7月9号,那天xhd迫于无奈要从27号楼搬到3号楼,因为10号要封楼了.看着寝室里的n件物品,xhd开始发呆,因为n是一个小于2000的整数,实在是太多了,于是xhd决定随便搬2*k件过去就行了.但还是会很累,因为2*k也不小是一个不大于n的整数.幸运的是xhd根据多年的搬东西的经验发现每搬一次的疲劳度是和左右手的物品的重量差的平方成正比(这里补充一句,xhd每次搬两件东西,左手一件右手一件).例如xhd左手拿重量为3的物品,右手拿重量为6的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.现在可怜的xhd希望知道搬完这2*k件物品后的最佳状态是怎样的(也就是最低的疲劳度),请告诉他吧.
每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.
Sample Input
2 1
1 3
Sample Output
4
其实这道题的思路要讲清楚还是挺复杂的(因为某人是蒟蒻的原因)
首先我们来分析最低疲劳度,如果采用贪心的思路,每次选重量最相近的两个物体搬的话,实际上是无法得到最低疲劳度的,
比方说有四个物体,搬两次,重量是 1 2 2 3, 按照贪心来说是5,可最低疲劳度显然是2,我们可以比较容易的发现想要得到最低疲劳度每次选取的两个物品至少要是重量相近的,所以要先排序,之后不难证明要得到最低疲劳度,每次选取的应当是排序后相邻的两个物品(可以看正方形的面积,非常清楚)。
那么我们得到了所选取的物品必须排序后相邻的结论后,接下来是对问题的分类了,从n个物品搬一次来看,子问题根据搬不搬最后一个物品分为两类(这个地方我其实省去了从2个、3个、4个物品递推的过程),就搬一次而言,如果搬了最后一个物品那么就是搬最后两个物品,如果没搬就变成了n-1个物品搬一次的子问题了。那么这完全符合dp的特点,接下来是从n个物品搬k次,同样是根据搬不搬最后一个物品分为两类,如果搬了最后一个物品的话,那么就是n-2个物品搬k-1次的最低疲劳度加上搬最后两个物品的疲劳度,(n-2个物品搬k-1次也是子问题,本身也含有子问题),如果没搬最后一个物品的话,就是n-1个物品搬k次,这也是子问题。
dp问题的特点都是自顶向下的分析,自下而上的实现。(个人理解)
代码实现:
首先设置f[][]二维数组用于存最优子结构,f[n][k]就是前n个物品搬k次的最低疲劳度。
直接上代码吧
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[2005];
int f[2005][1005];
int main()
{
while (cin >> n >> k)
{
memset(f, 0x3f, sizeof(f));//这里要注意,由于dp是从小到大,所以要把数组的初始值置为无穷大
for (int i = 0; i <= n; i++)
{
f[i][0] = 0;//一次不搬是最初始的子结构,疲劳度显然是0
}
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
sort(a + 1, a + 1 + n);//排序
for(int i=2;i<=n;i++)
{
for(int j=1;j<=i/2;j++)
{
f[i][j]=min(f[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]),f[i-1][j]);//状态转移方程,思路上面有分析
}
}
printf("%d\n", f[n][k]);
}
}