poj1743硬币COINS三种解法(1个动态规划+2个搜索)

6 篇文章 0 订阅
2 篇文章 0 订阅

Description

People in Silverland use coins.They have coins of value A1,A2,A3…An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn’t know the exact price of the watch.
You are to write a program which reads n,m,A1,A2,A3…An and C1,C2,C3…Cn corresponding to the number of Tony’s coins of value A1,A2,A3…An then calculate how many prices(form 1 to m) Tony can pay use these coins.

Input

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3…An,C1,C2,C3…Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

Sample Output

8
4

描述

银地人使用硬币。他们有价值A1,A2,A3的硬币…一个银地元。一天,托尼打开他的钱箱,发现有一些硬币。他决定在附近一家商店买一块很漂亮的手表。他想支付确切的价格(没有变化),他知道价格不会超过m.但他不知道手表的确切价格。
你要写一个程序,读n,m,A1,A2,A3…A 和 C1,C2,C3…Cn 对应于托尼价值 A1、A2、A3 的硬币数量…然后计算多少价格(形式1到m)托尼可以支付使用这些硬币。

输入

输入包含多个测试用例。每个测试用例的第一行包含两个整数 n(1<\n<=100),m(m<=100000)。第二行包含 2n 整数,表示 A1、A2、A3…A,C1,C2,C3…Cn (1<=Ai<=100000,1<=Ci<=1000)。最后一个测试用例后跟两个零。

输出

对于每个测试用例,在一行上输出答案。

样本输入

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

样品输出

8
4

题解一:动态规划

思想是动态规划,主要的两个数值:用一个数组f[i]表示面值i能否达到,g[i]表示到达f[i]用了g[i]个a[i],用来控制数值个数不要用超。对于每一个a[i]看使用它与其余不使用它的时候能加出多少种硬币和。因为价值最大为m,所以控制在1–m范围内,用a[i]和其他数值组合能达到多少种价值。
控制条件:
1~如果第f[j]个可以达到,也就是f[j]==1,那么说明在他的基础上可以进行加其他数
2~j+a[i]<=m,保证不会越过最大价值
3~f[j+a[i]]==0保证这个数还没有达到过,这样就可以继续使用a[i]去达到其他的数,不浪费a[i]的个数了
4~g[j]<c[i]保证上一步达到g[j]的时候用a[i]后,a[i]还有剩余,以用来组成其他的价值,并且g[j+A[i]]是在g[j]的基础上加了个a[i],所以g[j+a[i]]使用的a[i]的个数要比第j个价值使用a[i]的个数即g[j]大一。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int f[100100], cnt;//f[i]表示面值为i能否达到
int a[110], c[110];//记录单个面值和数量
int g[100100];//g[i]表示到达f[i]用了g[i]个a[i]
int main()
{
    int n, m;
    while (cin>>n>>m)
    {
        if (m == 0 && n == 0)break;//如果n,m都等于0则跳出循环
        cnt = 0;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i <= n; i++)
            cin >> c[i];
        memset(f, 0, sizeof(f));//先都赋值为达不到
       
        f[0] = 1;
        for (int i = 1; i <= n; i++)
        {
            memset(g, 0, sizeof(g));
            for (int j = 0; j <= m; j++)
            {
                if (f[j] && (f[j + a[i]] == 0) && j + a[i] <= m&&g[j]<c[i])
                 {
                    f[j + a[i]] = 1;
                    g[j + a[i]] = g[j]+1;
                }
            }
                
        }   
        for (int i = 1; i <= m; i++)
            if (f[i])cnt++;//如果面值可以达到则可能数++
        cout << cnt << endl;
    }
    return 0;
}

题解二:搜索(这个题提交TLE)但学个思想也挺好。

思想就是先把所有硬币排到数组b中,
比如样例一:数组a[ ]:1 2 4,个数c[ ]:1 1 2
那么b数组里存的就是1 2 4 4,硬币的总个数为sum[n]
之后就是普通的搜索了
dfs参数t表示现在已经用了t个硬币,搜索的结束条件是t>sum[n]
对于每一个b数组中的硬币,都有选与不选两种选择,如果不选,直接dfs(t+1)就好了,如果选,并且该价值之前没有达到过并且,满足现在价值res<=m,这样的话,可以达到的价值ans++,并将f[res]标记为1,表示已经达到过了。选完此步,继续dfs(t+1),之后将res恢复原状,相当于走此树枝的另一个子树。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int b[100100];
int f[100100];//f[i]表示面值为i能否达到
int a[110], c[110];//记录单个面值和数量
int sum[110];//存储前i项面值数量和
int ans,res;
int n, m;
void dfs(int t)
{
    if (t > sum[n])
    {
        return ;
    }
    for (int i = 0; i <= 1; ++i)
    {
        res += b[t]*i;//这里也可以写成if(i==1)res+=b[t];
        if (!f[res]&&res<=m)//如果价值大小res还没有达到过
        {
            ++ans; 
            f[res] = 1;
        }
        dfs(t + 1);
        //这里也可以写成if(i==1)res-=b[t];
        res -= b[t]*i; //如果走到这儿,说明不是向下寻找的
    }
}
int main()
{
    while (cin >> n >> m)
    {
        res = 0;
        ans = 0;
        memset(sum, 0, sizeof(sum));
        memset(f, 0, sizeof(f));
        if (m == 0 && n == 0)break;//如果n,m都等于0则跳出循环
        for (int i = 1; i <= n; i++)
            cin >> a[i];//输入价值
        for (int i = 1; i <= n; i++)//输入第i种价值的个数
        {
            cin >> c[i];
            sum[i] = sum[i - 1] + c[i];//前i种价值的总个数
        }
        for (int i = 1; i <= n; ++i)//把所有数值放到b数组中,b数组下标从1开始,对b数组进行搜索 
        {
            for (int k = sum[i - 1] + 1; k <= sum[i]; ++k)
            {
                b[k] = a[i];
            }
        }
        sort(b + 1, b + 1 + sum[n]);//b数组排序 
        dfs(1);
        cout <<ans-1 << endl;//这个地方减一是因为搜索会把和是0的情况算上,所以要-1
    }
    return 0;
}

题解三:搜索

这个思想和题解二差不多,不一样的就是搜索的部分是直接对b数组进行遍历。注释在代码中也很清晰,就不赘述了,哦,这个提交也是超时的

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int b[100100];//后来用的数组
bool f[110110];//f[i]表示面值为i能否达到
int a[110], c[110];//记录单个面值和数量
int sum[110];
int n, m;
int ans ,res;//所求硬币和种类
//n种硬币,m为最大总价值,res为现在已经到达的总价值,num为已经使用过的硬币个数
void dfs(int num)
{
   if (num > sum[n]) return ;
   if (!f[res]&&res<=m)//如果现在达到的总价值res之前没达到过,并且现在的总价值小于等于m
   {
       ++ans;//硬币和的种类++
       f[res] = 1;
   }
    for (int i = num+1; i <= sum[n]; ++i)//从上次遍历的地方往后遍历
    {
        res += b[i];
        if(f[res-b[i]]) dfs(i);//只能在上一个能达到的前提下,才能继续往下走
        res -= b[i];
    }
}
int main()
{
    while (cin >> n >> m)
    {
        ans = 0;
        res = 0;
        memset(f, 0, sizeof(f));
        memset(sum, 0, sizeof(sum));
        if (m == 0 && n == 0)break;//如果n,m都等于0则跳出循环
        for (int i = 1; i <= n; i++)
            cin >> a[i];//输入价值
        for (int i = 1; i <= n; i++)//输入第i种价值的个数
        {
            cin >> c[i]; 
            sum[i] =sum[i-1]+ c[i];//前i种价值的总个数
        }
        for (int i = 1; i <= n; ++i)
        {
            for (int k = sum[i - 1]+1; k <= sum[i]; ++k)
            {
                b[k] = a[i];  
            }
        }
        sort(b + 1, b + 1 + sum[n]);
        dfs(0);
        cout<<ans-1<<endl;//这个地方减一是因为搜索会把和是0的情况算上,所以要-1
    }
    return 0;
}
/*3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值