原文转自:haha593572013
原文地址链接:http://blog.csdn.net/crazy_ac/article/details/7869411
http://acm.hdu.edu.cn/showproblem.php?pid=2126
题意:给你n种物品以及每种物品的价格,m的钱,问你在满足买最多种物品的前提下,有多少种不同的够买方案
解法1:
首先献上我的比较挫的方法,也是大众化的方法,dp[i][j][k]表示前i种物品,买了j个,花了小于等于k的钱的时候的方案数
因为是小于等于k,所以初始化的时候要注意哦。
转移的时候的情况dp[i][j][k]的情况会有两种,一种是放入了第i件,一种是没有放入第i件,最后都达到了J件,这两种可能合起来就是总方案数.
ps:由动态方程到实现的过程中,一般意识是,类似于数学归纳法,知道第一层的状态不断地递推下去,所以,只需要确保第一层状态已经建立,然后开始递推,在递推的过程中需要注意有没有冒着重复计算的风险。
- dp[i][j][k]+=dp[i-1][j-1][k-p[i]];
- dp[i][j][k]+=dp[i-1][j][k];
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- using namespace std;
- int dp[35][35][510];
- int p[35];
- int main()
- {
- int t,m,n;
- scanf("%d",&t);
- while(t--)
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++) scanf("%d",&p[i]);
- memset(dp,0,sizeof(dp));
- for(int i=0;i<=n;i++)
- {
- for(int j=0;j<=m;j++)
- dp[i][0][j]=1;
- }
- for(int i=1;i<=n;i++)
- {
- for(int j=1;j<=i;j++)
- {
- for(int k=m;k>=0;k--)
- {
- if(k>=p[i]) dp[i][j][k]+=dp[i-1][j-1][k-p[i]];
- dp[i][j][k]+=dp[i-1][j][k];
- // printf("i=%d j=%d k=%d %d\n",i,j,k,dp[i][j][k]);
- }
- }
- }
- int ops=-1,kinds=-1;
- for(int i=n;i>=1;i--)
- {
- for(int j=i;j>=1;j--)
- {
- for(int k=m;k>=0;k--)
- {
- if(dp[i][j][k])
- {
- //printf("%d %d %d\n",i,j,k);
- kinds=j;
- ops=dp[i][j][k];
- break;
- }
- }
- if(kinds!=-1) break;
- }
- if(kinds!=-1) break;
- }
- if(ops!=-1)
- {
- printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",ops,kinds);
- }
- else printf("Sorry, you can't buy anything.\n");
- }
- return 0;
- }
解法二:直接变成rank2了!!!
其实也很简单,可以这样设计状态开一个dp[30][500][2]的数组,dp[i][j][0]就表示前i个物品,花了 <= j 的钱,最多可以买到的纪念品数量,dp[i][j][1]就表示买到最多纪念品前提下的方案数了,转移的时候就按照关键字来转移,
先判断能否增加所买的纪念品的种类数,
再判断种类数相同的时候的情况
- #include<cstdio>
- #include<cstring>
- #include<iostream>
- using namespace std;
- int dp[31][501][2];
- int v[31];
- int main()
- {
- int t,m,n;
- scanf("%d",&t);
- while(t--)
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++) scanf("%d",&v[i]);
- memset(dp,0,sizeof(dp));
- for(int i=0;i<=m;i++) dp[0][i][1]=1;
- for(int i=0;i<=n;i++) dp[i][0][1]=1;
- for(int i=1;i<=n;i++)
- {
- for(int j=0;j<v[i];j++) dp[i][j][0]=dp[i-1][j][0],dp[i][j][1]=dp[i-1][j][1];
- for(int j=v[i];j<=m;j++)
- {
- if(dp[i-1][j-v[i]][0]+1>dp[i-1][j][0])
- {
- dp[i][j][0]=dp[i-1][j-v[i]][0]+1;
- dp[i][j][1]=dp[i-1][j-v[i]][1];
- }
- else if(dp[i-1][j-v[i]][0]+1==dp[i-1][j][0])
- {
- dp[i][j][0]=dp[i-1][j][0];
- dp[i][j][1]=dp[i-1][j][1]+dp[i-1][j-v[i]][1];
- }
- else
- {
- dp[i][j][0]=dp[i-1][j][0];
- dp[i][j][1]=dp[i-1][j][1];
- }
- }
- }
- if(dp[n][m][0]) printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[n][m][1],dp[n][m][0]);
- else printf("Sorry, you can't buy anything.\n");
- }
- return 0;
- }
解法三:rank1
优化是永无止境的~ 突然发现只有最后一层的状态有用,那就用滚动数组试试吧
结果,直接跑到了rank1
精益求精,截图留念一下
- #include<cstdio>
- #include<cstring>
- #include<iostream>
- using namespace std;
- int dp[501][2], v[31];
- int main()
- {
- int t,m,n;
- scanf("%d",&t);
- while(t--)
- {
- scanf("%d%d",&n,&m);
- for(int i=1;i<=n;i++) scanf("%d",&v[i]);
- memset(dp,0,sizeof(dp));
- for(int i=0;i<=m;i++) dp[i][1]=1;
- for(int i=1;i<=n;i++)
- {
- dp[0][1]=1;
- for(int j=m;j>=v[i];j--)
- {
- if(dp[j-v[i]][0]+1>dp[j][0])
- {
- dp[j][0]=dp[j-v[i]][0]+1;
- dp[j][1]=dp[j-v[i]][1];
- }
- else if(dp[j-v[i]][0]+1==dp[j][0])
- {
- dp[j][1]=dp[j][1]+dp[j-v[i]][1];
- }
- }
- }
- if(dp[m][0]) printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[m][1],dp[m][0]);
- else printf("Sorry, you can't buy anything.\n");
- }
- return 0;
- }
还有些人竟然可以用0kb的内存,- -!,难道是纯数学方法?
建议不通过注释看懂代码,我看完之后懂了好多东西.
以下为注释代码:
<span style="font-size:18px;"># include<iostream>
# include<cstdio>
# include<cstring>
using namespace std;
/*
dp[i][j][0]=dp[i-1][j][0] || dp[i][j][0]=dp[i-1][j-p[i]][0];
if(j>=p[i]) dp[i][j][1]+=dp[i-1][j-p[i]][1]
dp[i][j][1]+=dp[i-1][j][1]
*/
int dp[35][510][2],v[35];
int main()
{
int t,m,n,i,j,k;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++) scanf("%d",&v[i]);
memset(dp,0,sizeof(dp));
//方案数0层初始化
for(i=0;i<=n;i++) dp[i][0][1]=1;
for(i=0;i<=m;i++) dp[0][i][1]=1;
for(i=1;i<=n;i++)
{
for(j=0;j<v[i];j++)//二维数组确定了各阶段状态的独立性,无须从j=m开始,第i个不放下的状态转移
dp[i][j][0]=dp[i-1][j][0],dp[i][j][1]=dp[i-1][j][1];
for(j=v[i];j<=m;j++)//第i个放下的状态转移
{
if(dp[i-1][j-v[i]][0] +1 >dp[i-1][j][0])//可以放下,而且放下之后比之前的个数多
{
dp[i][j][0]=dp[i-1][j-v[i]][0]+1;
dp[i][j][1]=dp[i-1][j-v[i]][1];
}
else if( dp[i-1][j-v[i]][0]+1==dp[i-1][j][0] )//可以放下,而且放下后的个数与原个数相等
{
dp[i][j][0]=dp[i-1][j][0];
dp[i][j][1]=dp[i-1][j][1]+dp[i-1][j-v[i]][1];
}
else//可以放下,而且放下后的个数比原个数小
{
dp[i][j][0]=dp[i-1][j][0];
dp[i][j][1]=dp[i-1][j][1];
}
}
}
if(dp[n][m][0])//至少可以购买一个
printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[n][m][1],dp[n][m][0]);
else
printf("Sorry, you can't buy anything.\n");
}
return 0;
}
# include<iostream>
# include<cstdio>
# include<cstring>
using namespace std;
int dp[510][2],p[35];
int main()
{
int t,i,j,n,m;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&p[i]);
memset(dp,0,sizeof(dp));
for(i=0;i<=m;i++)
dp[i][1]=1;
for(i=1;i<=n;i++)//用01背包的解法,重量变成了个数,利用个数的变化与方案数的联系求得方案数
for(j=m;j>=p[i];j--)//保证状态独立性
{
if( dp[j-p[i]][0]+1>dp[j][0] )//可以放下,而且放下之后个数比原来大
{
dp[j][0]=dp[j-p[i]][0]+1;
dp[j][1]=dp[j-p[i]][1];
}
else if(dp[j-p[i]][0]+1==dp[j][0] )//放下之后个数和原谅相等
dp[j][1]=dp[j-p[i]][1]+dp[j][1];
}
if(dp[m][0])
printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[m][1],dp[m][0]);
else
printf("Sorry, you can't buy anything.\n");
}
return 0;
}</span>