HDU1421:搬寝室(线性dp)
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1421
又是一道,没有思想的题,看了题解,我发现我的dp题几乎都看了题解,我总是想不好状态转移方程,汗颜,以后怎么比赛啊。
先排序,然后说一个数学问题。
首先,要怎么搬呢?即每一对要怎么取?如果有abcd四个数,且a<b<c<d,应该是取ab,cd好呢还是ac,bd好?抑或是bc,ad好呢?答案是第一种,因为:
(a-b)^2+(c-d)^2 < (a-c)^2+(b-d)^2
(a-b)^2+(c-d)^2 < (a-d)^2+(b-c)^2
即每对物品都应是重量最为接近的物品,也就是说对n件物品排序后,每对物品都应该是连续的。
定义数组w[i]为搬第i对物品所消耗的疲劳值;数组dp[n][k]来表示在n件物品中搬k对的最佳状态,而达到这一状态的决策可能为:
- 第n件物品不搬,即在前n - 1件物品中搬k对,那么疲劳值仍为dp[n - 1][k];
- 第n件物品要搬,那么根据上面所证,第n - 1件物品也要同时搬,即在前n - 2件物品中搬k - 1对物品,再搬最后一对物品,那么疲劳值为dp[n - 2][k - 1] + w[n - 1]。
为使疲劳值最小,因此最佳策略为取两种决策中的最小值,即应使:
dp[n][k] = min(dp[n - 1][k], dp[n - 2][k - 1] + w[n - 1])
应该注意的是要考虑边界问题,dp[i][j]中:
- 当2 * j > i时,即要搬的数量超过了物品总量,这是不可能发生的,因此此时令dp[i][j]为无穷大;
- 当j == 0时,即在一对物品都没搬时,所需疲劳值应该是0,此时令dp[i][j] = 0。
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #include <queue> #include <math.h> #define inf 0x3f3f3f3f typedef long long ll; using namespace std; int n,k,w[2010],dp[2010][1010]; int main() { while(scanf("%d%d",&n,&k)!=EOF) { for(int i=1;i<=n;i++) { scanf("%d",&w[i]); } sort(w+1,w+1+n); for(int i=1;i<n;i++) { w[i]=w[i+1]-w[i]; w[i]=w[i]*w[i]; } for(int i=0;i<=n;i++) { for(int j=0;j<=k;j++) { dp[i][j]=inf; } } for(int i=0;i<=n;i++) dp[i][0]=0; //边界的处理 for(int i=2;i<=n;i++) { for(int j=1;j*2<=i;j++) { dp[i][j]=min(dp[i-2][j-1]+w[i-1],dp[i-1][j]); } } printf("%d\n",dp[n][k]); } return 0; }