采药(动态规划背包问题总结)
做了NOIP的题,是动态规划背包问题的典例。
采药
输入格式:
第一行有2个整数T(1≤T≤1000)和M(1≤M≤100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式:
1个整数,表示在规定的时间内可以采到的草药的最大总价值。
状态转移方程:
vis[i][j]=vis[i-1][j-xt[i]]+xw[i],当j>=xt[i]; vis[i][j]=vis[i-1][j],当j<xt[i];
其中vis[i][j]表示采摘前i个药时最大花费j时所能得到的最大价值,xw[i]是第i个药的价值,xt[i]是第i个药的费时。
代码如下:
for(int i=1;i<=m;i++) for (int j = 1;j <= t;j++) { if (j >= xt[i]) vis[i][j] = max(vis[i - 1][j - xt[i]] + xw[i], vis[i - 1][j]); else vis[i][j] = vis[i - 1][j]; }
我们可以发现,vis数组每次i递进1时只用到上一层的i-1,所以可以用滚动数组替换,因为大的j(vis[i][j])在计算时要用到小的j(vis[i-1][j-?]),故要从t向下开始替换,以免替换之前的vis[i-1][?]被换成了vis[i][?]。状态转移方程如下:
vis[j]=vis[j-xt[i]]+xw[i],当j>=xt[i];
vis[j]=vis[j],当j<xt[i];
代码如下:
for(int i=1;i<=m;i++) for (int j = t;j >= 1;j--) { if (j >= xt[i]) vis[j] = max(vis[j - xt[i]] + xw[i], vis[j]); else vis[j] = vis[j]; }
又做了一个题,其中每种药的个数不限制,这样一来又要套一个循环vis[i][j]=max(vis[i-1][j-n*xt[i]]+n*xw[i],vis[i-1][j])其中n为选择该药的数量。
但提交后发现超时了,看了题解,发现可以用vis[j]=max(vis[j-xt[i]]+xw[i],vis[j])替换掉循环,其中vis[j-xt[i]]为替换过的值,实际上递推代替循环,但注意要从小的j到大的j更新vis的值,使得使用的vis[j-xt[i]]是被替换过的。
代码如下:
for(int i=1;i<=m;i++) for (int j = 1;j <= t;j++) { if(j>=xt[i]) vis[j] = max(vis[j - xt[i]] + xw[i], vis[j]); }