题目7.5链接搬宿舍
题目大意:从n物品中取出k对,使得每对重量之差平方的和最小。
思路:这道动态规划题目在做之前需要自己证明一下:每对物品取相邻物品(先排序),可以保证取得的物品重量之差的平方的和是最小的
。所以题解转换为:先排序,在取相邻物品。
可以设状态dp[i][j]表示在前j件物品中选择i对物品是最小的代价。对于物品j有两种选择:与物品j-1配对或不配对。不配对,则dp[i][j] = dp[i][j-1];配对,则dp[i][j] = dp[i-1][j-2]+(w[j-1]-w[j-2])^2
,(注意w是重量,下标从0开始)
,二者取最小。
同时需要注意:只有j>=ix2,dp[i][j]才有意义;且当j==ix2时,所有的物品均要使用。
代码如下:
#include
#include
using namespace std;
const int MAX = 2001;
const int M = 1<<15;
int dp[MAX][MAX];
int w[MAX];
int n, k;
void Init()
{
for(int i=0; i<=MAX; i++)
{
dp[0][i] = 0;
}
}
int main()
{
while(cin >> n >> k)
{
//初始化
Init();
for(int i=0; i<n; i++)
{
cin >> w[i];
}
//排序
sort(w, w+n);
//初始化
for(int i=2; i<=n; i+=2)
{
dp[i/2][i] = dp[(i-2)/2][i-2]+(w[i-1]-w[i-2])*(w[i-1]-w[i-2]);
}
//动态规划,注意下标
for(int i=1; i<=k; i++)
{
for(int j=i*2+1; j<=n; j++)
{
dp[i][j] = min(dp[i][j-1], dp[i-1][j-2]+(w[j-1]-w[j-2])*(w[j-1]-w[j-2]));
}
}
cout << dp[k][n] << endl;
}
return 0;
}
记忆化搜索代码:
#include
#include
#include
#include
using namespace std;
/* dp[i][j]表示在前i个物品中搬2*j个的最小疲劳度 */
const int MAX = 2005;
const int INF = 1<<29;
int w[MAX], dp[MAX][MAX/2];
int DP(int i, int j)
{
if(i<2*j) return INF;//无法搬出2*j的返回INF
int &ans = dp[i][j];
if(ans!=-1) return ans;
ans = min(DP(i-1, j), DP(i-2, j-1)+(w[i]-w[i-1])*(w[i]-w[i-1]));
return ans;
}
int main()
{
int n, k;
while(scanf("%d%d", &n, &k)!=EOF)
{
for(int i=1; i<=n; i++)
{
scanf("%d", &w[i]);
}
sort(w+1, w+n+1);
memset(dp, -1, sizeof(dp));
for(int i=0; i<=n; i++) dp[i][0] = 0;//初始化
printf("%d\n", DP(n, k));
}
return 0;
}
题目链接7.6Gready Tino
题目大意:从一个数列中选出两堆数,使得两堆数的总和相等,求满足该条件下,每一堆数的总和最大是多少。
思路:动态规划,重点在状态的构造选择。
dp[i][j] = max(dp[i-1][j-w[i]]+w[i], dp[i-1][j+w[i]]+w[i], dp[i-1][j]);
#include
#include
using namespace std;
const int OFFSET = 2000;//偏移量
const int INF = 1<<29;
int dp[101][4001];
int W[101];//重量
int main()
{
int T, cas = 0;
scanf("%d", &T);
while(T--)
{
int n;
scanf("%d", &n);
bool zero = false;//统计是否存在重量为0的柑橘
int cnt = 0;//计数器,记录有多少个重量非0的柑橘
for(int i=1; i<=n; i++)//输入n个柑橘的重量
{
scanf("%d", &W[++cnt]);
if(W[cnt]==0)
{
cnt--;//去除这个重量为0的柑橘
zero = true;//记录存在重量为0的柑橘
}
}
n = cnt;
//初始化所有dp[0][i]为负无穷,即不符合条件
for(int i=-2000; i<=2000; i++)
{
dp[0][i+OFFSET] = -INF;
}
dp[0][0+OFFSET] = 0;
for(int i=1; i<=n; i++)
{
for(int j=-2000; j<=2000; j++)
{
int tmp1 = -INF, tmp2 = -INF;//
//保证两堆的重量差值不超过2000
if(j+W[i]<=2000 && dp[i-1][j+W[i]+OFFSET]!=-INF)//可以放在第二堆
{
tmp1 = dp[i-1][j+W[i]+OFFSET] + W[i];
}
if(j-W[i]>=-2000 && dp[i-1][j-W[i]+OFFSET]!=-INF)//可以放在第一堆
{
tmp2 = dp[i-1][j-W[i]+OFFSET] + W[i];
}
//选出两堆重量最大的情况
if(tmp1<tmp2) tmp1 = tmp2;
if(tmp1<dp[i-1][j+OFFSET]) tmp1 = dp[i-1][j+OFFSET];
dp[i][j+OFFSET] = tmp1;
}
}
printf("Case %d: ", ++cas);
if(dp[n][0+OFFSET]==0)
{
if(zero) printf("0\n");
else printf("-1\n");
}
else
printf("%d\n", dp[n][0+OFFSET]/2);
}
return 0;
}
《王道》上的三道背包题包括了0-1背包问题,完全背包问题和多重背包问题,但均是以0-1背包为基础,转换为0-1背包或以其为基础来解决,具有很大的参考价值。
题目链接7.7采药
题目大意:给定规定时间,有多种草药(每一种只有一个,且有不同的采药时间和价值),问在规定时间内怎样采到的草药价值最高。
思路:将问题抽象为背包问题。时间等价于背包容量,即如何将东西装入背包且获得价值最大。
方法一:二维数组
#include
#include
using namespace std;
const int MAX = 101;
struct Node
{
int w, v;
};
Node u[MAX];
int dp[MAX][MAX*10];
void Init()
{
memset(dp, 0, sizeof(dp));//也可以只对dp[0][0]赋值为0
}
int main()
{
int T, M;
while(cin >> T >> M)
{
for(int i=1; i<=M; i++)
{
cin >> u[i].w >> u[i].v;
}
Init();
for(int i=1; i<=M; i++)
{
for(int j=1; j<=T; j++)
{
if(j>=u[i].w)
{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-u[i].w]+u[i].v);
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
cout << dp[M][T] << endl;
}
return 0;
}
方法二:一维数组
#include
#include
using namespace std;
const int MAX = 101;
struct Node
{
int w, v;
};
Node u[MAX];
int dp[MAX*10];
void Init()
{
memset(dp, 0, sizeof(dp));//也可以只对dp[0]赋值为0
}
int main()
{
int T, M;
while(cin >> T >> M)
{
for(int i=1; i<=M; i++)
{
cin >> u[i].w >> u[i].v;
}
Init();
for(int i=1; i<=M; i++)
{
for(int j=T; j>=u[i].w; j--)//从后向前
{
dp[j] = max(dp[j], dp[j-u[i].w]+u[i].v);
}
}
cout << dp[T] << endl;
}
return 0;
}
题目7.8链接Piggy-Bank
题目大意:有一个存储罐,告知其空时的重量和当前的重量,并给定一些钱币的价值和相应的的重量,求存储罐中最少有多少现金。
思路:
基本与0-1背包相同,只是遍历方向相反。
#include
#include
#include
using namespace std;
const int MAX = 600;
const int INF = 1<<28;
struct Node
{
int w, v;
};
Node u[MAX];
int dp[10001];
void Init(int n)
{
dp[0] = 0;
for(int i=1; i<=n; i++)
dp[i] = INF;
}
int main()
{
int T, E, F, n, W;
scanf("%d", &T);
while(T--)
{
//cin >> E >> F;
scanf("%d%d", &E, &F);
W = F-E;
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d%d", &u[i].v, &u[i].w);
//cin >> u[i].v >> u[i].w;
}
//注意初始化时要根据参数大小选择初始化范围,否则超时
Init(W);
//相当于先按种类来放
for(int i=1; i<=n; i++)
{
for(int j=u[i].w; j<=W; j++)
{
dp[j] = min(dp[j], dp[j-u[i].w]+u[i].v);
}
}
if(dp[W]>=INF)
{
printf("This is impossible.\n");
}
else
{
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[W]);
}
}
return 0;
}
题目7.9链接珍惜现在,感恩生活
题目大意:你有n元钱,市场上有m中大米,每一种大米均有其每袋的价格,每袋的重量以及对应种类大米的袋数。问你最多可以买多重的大米。
思路:
多重背包可以直接看成0-1背包来解决,即将每一种的每袋大米均看成不同种类,但可能使得种类太多,增大了复杂度。
通过将每一种大米拆若干组,分成1,2,4,8…k-2 ^c +1袋 (其中,c为使k-2^c+1大于0的最大整数)。可以简化其复杂度,并且这些组拼接起来可以组合成任意的数量。
w[i]代表第i种米的重量,v[i]代表第i种米的价钱。
代码:
#include
#include
using namespace std;
const int MAX = 101;
int dp[MAX];
struct Node
{
int w, v;
};
Node u[MAX*20];
void Init()
{
memset(dp, 0, sizeof(dp));
}
int main()
{
int T, n, m, w, v, k;
cin >> T;
while(T--)
{
cin >> n >> m;
int cnt = 0;//拆分后的总数量
for(int i=1; i<=m; i++)
{
cin >> v >> w >> k;
int c = 1;
while(k-c>0)
{
k -= c;
u[++cnt].w = c*w;
u[cnt].v = c*v;
c *= 2;
}
u[++cnt].w = w*k;
u[cnt].v = v*k;
}
Init();
for(int i=1; i<=cnt; i++)
{
for(int j=n; j>=u[i].v; j--)
{
dp[j] = max(dp[j], dp[j-u[i].v]+u[i].w);
}
}
cout << dp[n] << endl;
}
return 0;
}
不优化
三重循环,限制每一种大米的袋数。
//HDU 2191 多重背包
#include
int val[110],wei[110],count[110],dp[110];//价格,重量,袋数。动态背包
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;// 钱数,种类
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",val+i,wei+i,count+i);
}
for(int i=0;i<=n;i++) dp[i]=0;//初始为0
for(int i=0;i<m;i++) //第 i 种大米
for(int k=1;k<=count[i];k++) //大米 i 放入的次数
for(int j=n;j>=val[i];j--) // 动态规划
dp[j]=max(dp[j],dp[j-val[i]]+wei[i]);
printf("%d\n",dp[n]); //数组dp存的是重量
}
return 0;
}