每日一题(11): poj1276

1. 本来觉得很简单,却耗费了一天半的时间。本来做完1837之后感觉已经明白了怎么做。但是事实仿佛并非如此。当然,这也有自己的一点执念。

2. 这几天体会到,写程序就像做数学题一样,很忌讳一算就错。程序也是,不能想起来觉得这样这样,但是写起来没有把握对。就像前两天有个演讲说的那样,印度的程序很厉害,因为所有人写的都一样。程序是工业产品,流水线上的产品,是有标准答案的。

3. 总觉得不要怕。自己在这次中遇到了很严重的问题,不知道怎么错的。是自己的思路。要是以往,也许就是看看网上别人的思路,照着别人的思路写。这样,即使写不对还可以有答案看。但是这次自己逼着自己去一点一点思考,到底哪里有问题。程序就是这样,肯定是思路的不严谨。要勇敢,敢于啃硬骨头。一定要想明白。这对于上面的自信很重要。一定要将来,自己写的代码有把握不出错。

4. 说这道题,其实就是多重背包问题。背包九讲里面说的很好。就不多说了。只是我写了好几个版本,包括原始的,暴力,二进制优化的。

5.自己的版本一直过不了。因为超时。原来是因为有问题,自己思路错了。

6. 这道题是多个测试案例。不要忘了。否则就是wa。但是题目里面没有

下面是各个版本的代码:

7. 首先是自己的思路,很基本的三层循环:

 #include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int n;
int c;
int num[20];
int cash[20];
int dp[11][100005];
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d%d",&c,&n))
{
//cin>>c>>n;
//这里其实可以不要。只是为了剪枝。
for(int i = 1; i<=n; i++)
{
cin>>num[i]>>cash[i];

}
if(n==0||c==0)
{
cout <<0<<endl;
continue;
}
memset(dp,0,sizeof(dp));

for(int i = 1; i<=n; i++)
{
for(int j = cash[i]; j<=c; j++)
{

int temp=0;
int flag = 0;
for(int k = 0; k<=num[i]; k++)
{
//在此处错了好久。因为temp是要取最大的。所以要加if来判断。
if(j>=k*cash[i])
{
if(temp<dp[i-1][j-k*cash[i]] + k*cash[i])
temp = dp[i-1][j-k*cash[i]] + k*cash[i];
//flag=1;
//break;
}

}
//原来把这写到里面了。而且k没有去0,这样就麻烦了。
if(temp>dp[i-1][j])
dp[i][j] = temp;
else dp[i][j] =dp[i-1][j];
}

}

cout <<dp[n][c]<<endl;
}
}

8, 上面的代码进行空间优化:还是超时,测试案例都可以过,就是超时。根据和网上大家思路的比对,自己确实是最慢的。而且也没有想到怎么剪枝。

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int n;
int c;
int num[20];
int cash[20];
int dp[100005];
int main()
{
//freopen("input.txt","r",stdin);
//没有while是肯定错的。
while(~scanf("%d%d",&c,&n))
{
//scanf("%d%d",&c,&n);

for(int i = 1; i<=n; i++)
{
cin>>num[i]>>cash[i];

}
//cout<<"test: "<<c<<","<<n<<endl;
//cin>>c>>n;
if(c==0)
{
cout << 0<<endl;
continue;
}
if(n==0)
{
cout <<0<<endl;
continue;
}


memset(dp,0,sizeof(dp));



for(int i = 1; i<=n; i++)
{
for(int j = c; j>=1; j--)
{

int temp=0;
//int flag = 0;
for(int k = 0; k<=num[i]; k++)
{

if(j>=k*cash[i])
{
if(temp<dp[j-k*cash[i]] + k*cash[i])
temp = dp[j-k*cash[i]] + k*cash[i];
//flag=1;
//break;
}
//if(j+k*cash[i]>c) break;
}

if(temp>dp[j])
dp[j] = temp;
//cout<<"test:"<<dp[j]<<endl;
//else dp[j] =dp[j];
}

}


cout <<dp[c]<<endl;
}
}
9. 在网上看到的暴力解法:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int n;
int c;
int num[20];
int cash[20];
int dp[100005];
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d%d",&c,&n))
{
for(int i = 1; i<=n; i++)
{
cin>>num[i]>>cash[i];

}
//cout<<"test: "<<c<<","<<n<<endl;
//cin>>c>>n;
if(c==0)
{
cout << 0<<endl;
continue;
}
if(n==0)
{
cout <<0<<endl;
continue;
}


memset(dp,0,sizeof(dp));
int maxn = 0;
dp[0] = 1;
for(int i =1; i<=n;i++)
{
//for(int j = maxn; j<=c; j++)
//因为其他都是0//这里是可以用maxn的,其实其他的方法也可以。只是没有maxn,这里正好求出来了每次的最大值。另外,从现在的全部区间,求更大区间,应该可以理解。
for(int j = maxn; j>=0; j--)
{
if(dp[j]==1)
{
for(int k = 1; k<=num[i]; k++)
{
int temp = j+k*cash[i];
if(temp>c)
continue;
dp[temp] = 1;
if(temp>maxn)
maxn = temp;
}
}
}
}

cout <<maxn<<endl;
}
}

10. 多重背包的标准写法:

#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;
int dp[100005];
int cash[150];
//int num[20];

int c,n;
int count;
int main()
{
freopen("input.txt","r",stdin);
while(~scanf("%d%d",&c,&n))
{
memset(dp,0,sizeof(dp));
int numTemp,cashTemp;
count = 1;
for(int i = 1; i<=n; i++)
{
cin>>numTemp>>cashTemp;
if(numTemp==0) continue;
cash[count] = cashTemp;
count++;
if(numTemp-1>0)
{
int countTemp = numTemp-1;
int k = 2;
while(countTemp>0)
{
countTemp = countTemp-k;
//不能把这里的代码去掉。因为如果去掉了,总数会少
if(countTemp<0)
{
cash[count++] = cashTemp*(countTemp+k);

break;
}

cash[count++] = cashTemp * k;
k = k*2;
}

}
}

for(int i = 1; i <count; i++)
{
for(int j = c; j >= 0; j--)
{
if(j>=cash[i])
if(dp[j-cash[i]]+cash[i]>dp[j])
dp[j]=dp[j-cash[i]]+cash[i];
}

}
cout<<dp[c]<<endl;

}
}

11. 这里总结一下背包问题的几个点,便于以后写。那就是首选空间优化过的,时间上优化过的。也就是最好的算。完全和01在第二层循环上不同。多重在输入上不同。还有,就是第一层循环的起始和终点一般没有可优化的地方。但是二层,可以。一般起始或者终点可以优化。而且,要注意顺序。还有就是dp[j]是等于或者加等于起始都可以。因为每个点都知访问一次。

12. 如果要记录路径的话,应该就可最大序列差不多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值