首页 > 试题广场 >

拼凑硬币

[编程题]拼凑硬币
  • 热度指数:6725 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 128M,其他语言256M
  • 算法知识视频讲解

小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^K的硬币,所以小Q拥有的硬币就是1,1,2,2,4,4,8,8,…。小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少种方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案)。

 


输入描述:
输入包括一个整数n(1≤n≤10^18),表示小Q需要支付多少钱。注意n的范围。


输出描述:
输出一个整数,表示小Q可以拼凑出n元钱放的方案数。
示例1

输入

6

输出

3
import sys
import math
n = int(sys.stdin.readline())
mem = {-1:0,0:1,1:1}

def get_num(n):
    if n in mem:
        return mem[n]
    else:
        m = int(math.log2(n))
        ans = get_num(n-2**m) + get_num(2**(m+1)-2-n)
        mem[n] = ans
        return ans
print(get_num(n))

对题目进行分析不难发现应该是使用递推法,然后要点有两个
1.递推时需要取反,具体来说,对于 n = 5,我们用1个4,那么就有a1种可能,如果我们不用4,那么正着分析很难。但是1,1,2,2最多能凑6,那么我们考虑从这里面删去1凑5,那么方法树还是a1.
2.具体实现时由于n 过于大,所以不能直接申请数组,会直接超时,这里我们使用动态规划的记忆化搜索策略,使用辅助字典来缓存结果。
初步估计复杂度应该是O(logN).
编辑于 2019-01-14 22:02:30 回复(5)
证明一下大佬的思路:
假设 n = 1*a1+2*a2+…..+k*ak;
如果n为奇数:
n = 1 + 2*a2+….+k*ak;
n-1 = 2*a2+….+k*ak;
(n-1)>>1 = 1*a2+2*a3+…..+(k/2)*ak  = n>>1
设f(n)为(a1,a2,a3…ak)的解的总数
当n为奇数时,f(n)的解集(1,a2,a3,…ak),f(n>>1)的解集为(a2,a3….ak)
解集的个数相同。
当n为偶数时,f(n)的解集为(0,a2,a3…ak)加上(2,a2',a3'…ak')
n>>1 = 2*a2+….+k*ak; 解集为(a1,a2,a3…ak)
(n-2)>>1 =  1*a2'+2*a3'+…..+(k/2)*ak'  解集为(a2',a3'….ak')
所以 f(n) = f(n>>1) (n为奇数)
f(n) = f(n>>1)+f((n-2)>>1) (n为偶数)


编辑于 2019-09-04 09:11:15 回复(0)
n = int(input())
dic = {-1:0,0:1,1:1}
def Coin(n):
    if n in dic:
        return dic[n]
    elif n % 2 == 1:
        if n>>1 not in dic:
            dic[n>>1] = Coin(n>>1)
        return dic[n>>1]
    else:
        if n>>1 not in dic:
            dic[n>>1] = Coin(n>>1)
        if (n-2)>>1 not in dic:
            dic[(n-2)>>1] = Coin((n-2)>>1)
        return dic[n>>1] + dic[(n-2)>>1]
print(Coin(n))
发表于 2019-09-17 16:04:38 回复(0)
分析发现如果化成二进制,连续的10为2种(如2=2/1+1),110为3种(6=4+2/4+1+1/2+2+1),1110为4种(8+4+2/8+4+1+1/8+2+2+1+1/4+4+2+2+1+1),被00分割开的互不影响,末尾为1则低位到末尾连续的11只有一种情况(如3、7、15仅有一种情况),将输入化为二进制,统计X*1+0的情况乘起来,例如6=110有三种,10=1010有2*2=4种,26=11010=3*2=6种,在32以内的数验证都没问题,但是结果就不对,这样的分析有什么逻辑错误嘛😥😥😥😥
#include<iostream>
using namespace std;
int check(long long m)
{
    int num = 0, coin = 1;
    int c, cb;
    for (int i = 0;m != 0; i++)
    {
        c = m % 2;
        m /= 2;
        if (i == 0)
            cb = c;
        else
        {
            if (cb == 1 && c == 0)
                cb = 0;
            else if (cb == 0 && c == 0)
            {
                cb = 0;
                coin *= ++num;
                num = 0;
            }
            else if (cb == 0 && c == 1)
                num++;
        }
    }
    if (num)
        coin *= ++num;
    return coin;
}

int main()
{
    long long m;
    while (cin >> m)
    {
        cout << check(m) << endl;
    }
    return 0;
}

发表于 2019-04-04 10:21:25 回复(1)
#include <bits/stdc++.h>
using namespace std;

map<long, long> mp;

long F(long n){
    if(mp.find(n) != mp.end())
        return mp[n];
    long cnt = 0;
    if(n&1 == 1)
        cnt = F(n/2);
    else
        cnt = F(n/2) + F(n/2-1);
    mp[n] = cnt;
    return cnt;
}

int main(){
    mp[0] = mp[1] = 1;
    long n;
    scanf("%ld", &n);
    cout<<F(n)<<endl;
    return 0;
}

发表于 2020-11-13 00:43:29 回复(0)
n如果是奇数,说明必须只用一个1块钱,剩下只能用2 4 8,那么问题转移成了状态转移f(n) = f(n>>1),
此时2,4,8可以看做1,2,4,两者问题等价
如果n是偶数,那么可以分为两种情况,使用两个1块钱,或者不使用1块钱;两个一块钱的话,右移一位,需要减1
如果不使用的话还是右移一位。后面的问题也与1块钱无关,递归。再用一个map存储中间结果,解决重复性问题。
f(n) = f(n>>1) + f((n>>1) -1)
编辑于 2019-03-11 20:26:52 回复(7)

二楼思路实现一下

记忆搜索实现递归(直接递归会有大量重复计算容易栈溢出) 用map来保存

#include<iostream>
#include<map>
using namespace std;
map<long, long> m;

long solve(long n){  //记忆搜索法 
    if(m.count(n)) return m[n]; //如果已有直接返回
    long count = 0;
    if((n&1) != 1) count = solve(n>>1) + solve((n>>1)-1);  //n为偶数
    else count = solve(n>>1);  //n为奇数
    m[n] = count;
    return count;
}

int main(){
    m[0] = 1; m[1] = 1;  //初始值
    long n; cin>>n;
    cout<<solve(n)<<endl;
    return 0;
}
发表于 2019-05-23 21:17:11 回复(3)
动态规划
import java.util.*;

public class Main{
    public static void  main(String[] args){
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()){
            long n = sc.nextLong();
            System.out.println(helper(n));
        }
    }
    static long helper(long n){
        List<Integer> list = new ArrayList<>();
        while(n > 0){
            list.add((int)(n % 2));
            n /= 2;
        }
        long[][] dp = new long[list.size()][2];
        dp[0][list.get(0)]++;
        for(int i = 1 ; i < list.size() ; i++){
            if(list.get(i) == 0){
                dp[i][0] = dp[i - 1][0] + dp[i - 1][1];
            }else{
                dp[i][1] = dp[i - 1][0] + dp[i - 1][1];
                for(int j = i - 1 ; j >= 0 ; j--){
                    dp[i][0] += dp[j][0];
                    if(list.get(j) == 1){
                        break;
                    }
                }
            }
        }
        return dp[dp.length - 1][0] + dp[dp.length - 1][1];
    }
}

发表于 2021-09-05 18:57:28 回复(0)
#include <stdio.h>
#include <math.h>
int value[64];
unsigned long long int schemenum = 0;
void f(int bit, int bittotal, int n)
{
    int i;
    if(bit)
    {
        for(i=0;i<3;i++)
        {
            value[bit] = i;
            f(bit-1, bittotal, n);
        }
    }
    else
    {       
        for(i=0;i<3;i++)
        {
            value[bit] = i;
            unsigned long long int sumvalue = 0;
            for (int j=0; j<bittotal+1; j++)
            {
                sumvalue += value[j] << j;
            }
            if (sumvalue == n)
                schemenum ++;
        }
    }
}

int main()
{
    unsigned long long int n;
    scanf("%llu",&n);
    int bit=floor(log((double)n)/log(2)) + 1;
    for (int j=0; j<64; j++)
    {
        value[j] = 0;
    }
    f(bit, bit, n);
    printf("%llu", schemenum);
}


发表于 2021-08-17 16:48:58 回复(1)

参考一二楼思路,简化了下代码,因为不需要有序,认为使用unordered_map更优。

#include <iostream>
#include <unordered_map>
using namespace std;

#define ll long long
unordered_map<ll, ll> m = {
    {1, 1},
    {2, 2}
};

int func(ll n) {
    if (m[n]) return m[n];
    int ans;
    if (n % 2) {
        ans = func(n >> 1);
    } else {
        ans = func((n - 2) >> 1) + func(n >> 1);
    }
    return m[n] = ans;
}

int main() {
    ll n;
    cin >> n;
    cout << func(n) << endl;
    return 0;
}
发表于 2021-03-25 15:53:17 回复(0)
只有m[0]是必要的初始值,因为m[1]再次进行递归也会得到m[0];

发表于 2020-11-23 17:20:28 回复(0)
Num = int(input())
dp = {0: 0, 1: 1, 2: 2}
def dfs(n):
    if n < 3:
        return dp[n]
    else:
        if n in dp:
            return dp[n]
        if n % 2 == 0:               # n为偶数时,要么选取0个1,要么选取2个1.
            dp[n] = dfs(n >> 1) + dfs((n-2) >> 1)
            return dp[n]
        else:                        # n为奇数时,单个1为必选项,转而考虑偶数n-1,
            dp[n] = dfs((n-1) >> 1)  # 此时偶数n-1完全由多个2的k(k>0)次幂相加而成
            return dp[n]             # 等式两边同除2,解集个数不变,可得转态转移方程
print(dfs(Num))
编辑于 2020-08-22 15:08:33 回复(0)
# 参考 desperado_xxx 提供的思路

n = input('')
n = int(n)

dp = {1: 1, 2: 2}

def fun(val):
    if dp.get(val):
        return dp[val]
    # n为奇数,则取一个1,剩下与(n-1)/2的取法一一对应
    if val % 2 == 1:
        if dp.get(val >> 1):
            dp[val] = dp.get(val >> 1)
        else:
            dp[val] = fun(val >> 1)
    # n为偶数,有两种情况
    # 1. 取两个1,剩下与(n-2)/2的取法一一对应
    # 2. 不取1,剩下与n/2的取法一一对应
    else:
        if not dp.get(val >> 1):
            dp[val >> 1] = fun(val >> 1)
        if not dp.get(val >> 1 - 1):
            dp[val >> 1 - 1] = fun((val >> 1) - 1)
        dp[val] = dp[val >> 1] + dp[(val >> 1) - 1]

    return dp[val]


print(fun(n))

编辑于 2020-08-01 18:10:03 回复(0)
我的初步想法是输入=2^a1+2^a2+2^a3...2^an;
这样输出就等于a1-(之后的个数)
#需要用一下迭代感觉
编辑于 2020-07-28 14:45:57 回复(0)
这题目的时间复杂度不是特别好估计,不过应该不会特别大,且空间复杂度<时间复杂度,所以也不用担心内存爆炸
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;

map<ull, ull> results;

ull solve(ull n){
    if(results.count(n)) return results[n];
    if(n%2){
        results[n] = solve(n/2);
    } 
    else{
        results[n] = solve(n/2) + solve(n/2-1);
    }
    return results[n];
}

int main(){
    ull n;
    cin>>n;
    results[1] = 1;
    results[0] = 1;
    cout<<solve(n);
    return 0;
}


发表于 2020-07-05 17:36:11 回复(0)
import math
import sys
n = int(sys.stdin.readline().strip())
plans = {0:1, 1:1}

def findPlans(n):
    if n < 0:
        return 0
    if n in plans:
        return plans[n]
    else:
        maxk = int(math.log2(n))
        # the 1th keypoint is find (5) in total sum is equal to find (sum-5) in total sum
        # also, sum is 2**(m+1)-2 = \sum_{k=0}^{m}{2**k*2} is the 2nd keypoint
        # So, this line means, use or not use the max-available-value coin
        nums = findPlans(n-2**maxk) + findPlans(2**(maxk)*2 - 2 - n)
        plans[n] = nums
        return nums
print(findPlans(n))

# sorry for English only since this pc doesn't have sougou
发表于 2019-03-12 07:58:49 回复(0)
// 时间复杂度过高,不能通过;没什么技巧,就是将和拆分成最长子串相加,然后通过这个子串去
// 反向求解
// 例如 6:
// 1、先找到最靠近的2幂次方,这里很容易知道是4,然后建立一个数组,以2的n次方的n对应数组下标
// 即1就对应a[0],2对应a[1],4对应a[2]…… 数组的长度为n+1(如果sum为2的幂次方,那么n+2),其中2^n = 4
// 2、然后找到4的最长子串,容易知道这里就是递归了,下面会说如何递归。最后结果是 1+1+2
// 3、另一个加数为 6-4 = 2.这里也算出2的最长子串,结果是 1+1
// 4、所以最后的子串是 1+1+1+1+2,这里不满足题意,所以要进行调整,
// 5、调整的话,就是将数组中元素超过2的元素相加,那么元素个数就-2,后一个元素个数就+1
// [4,1,0],此时4大于2,对应的数字为 1,那么由 1+1 = 2,a[0] 变为2,a[1]变为 2;此时a[1]大于2,结果就是[2,2,0]
   即变为 1+1+2+2,这个就满足题意
//6、对这个 1+1+2+2 进行解决,因为我们知道 1+1+2+2 -> 2+2+2(不满足) -> 2+4   1+1+2+2 -> 1+1+4 这种不断递归就可以就可以得出
// 有几种方案,那如何进行递归呢,首先我们知道到这一步时,数组中的元素已经是满足条件的了,即一开始的数组不会出现超过2的元素个数
// 所以我们可以对个数等于2的元素本身进行累加,那么相加后本身元素个数就-2,而后一个元素的个数加+1,这里跟调整是同理的;
// 在调整过程中可能出现个数超过2的个数,那么包含这个超过2的元素都是不符合条件的,那么就不用继续遍历下去,直接返回

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);

        if(sc.hasNextLine()){
            long sum = sc.nextLong();
            if(sum == 1){
                System.out.println(1);
            }
                 // 得出第一个最靠近和的2的幂数
            long f1 = calNumber(sum);
                  // 通过这个数可以知道最大的数就是本身,
            int length = calIndex(f1);
                  // 如果和本身就是 2 的幂次方,那么最大的数是和本身,那么长度还要加1
            if((sum & sum-1) == 0){
                length++;
            }
                  // 上面假设最大的数是4,那么可知2^2 = 4,那么数组长度就为3(2+1),此时a[2] 下标才不会越界
            long[] array = new long[length+1];
                  // 例如 8,那么是2的幂次方,所以要建立的数组是4位,因为才有a[3]
                  // 而对于20,最大的数是16,即2^4,所以要建立的数组是5位,才有a[4]

                  // 进行最长子串求解,结果放在传入的 array 中
            lastestHelp(sum,array);
                  // 调整array,因为会出现不符合条件的个数
            adjust(array);
                  // 通过调整后的array求解,
            int result = calHelp(array,0);
            System.out.println(result);
        }
    }
    protected static void lastestHelp(long i,long[] array){
        if(i == 1){
            array[0] ++;
            return;
        }
        long ftemp1 = calNumber(i);
        long ftemp2 = i - ftemp1;
        if(ftemp1 == ftemp2){
            array[calIndex(ftemp2)] ++;
            lastestHelp(ftemp1,array);
            return;
        }
        lastestHelp(ftemp1,array);
        lastestHelp(ftemp2,array);
    }

    protected static long calNumber(long sum){
        int i1 = 0;
        long i2 = 1;
        while(i2 < sum){
            i2 = 1L << i1;
            i1++;
        }
        i2 >>>= 1;
        return i2;
    }

    protected static int calIndex(long num){
        int result = 0;
        while((num & 1) != 1){
            num >>>= 1;
            result++;
        }
        return result;
    }

    protected static void adjust(long[] array){
        // 调整元素
        for(int i = 0;i<array.length;i++){
            while(array[i] > 2){
                array[i+1]++;
                array[i] -= 2;
            }
        }
    }

    protected static int calHelp(long[] array,int start){
        int result = 0;
        //通过数组算出结果
        for(int i = start;i<array.length;i++){
            if(array[i] >= 2){
                        // 通过深拷贝,保存原数组内容
                long[] tempArray = new long[array.length];
                System.arraycopy(array,0,tempArray,0,array.length);
                array[i+1] ++;
                array[i] -= 2;
                        // 每次查询从下一位开始,因为本身已经被上一个进行累加了,并且本身大于等于2
                result += calHelp(array,i+1);
                        // 上一步遍历如果本身元素大于2的话,在上一个遍历中会被累加分散,所以是满足题意的,但是到这一步时
                        // 已经变为原数组了,如果此时元素有大于3的话,就不用继续循环遍历了,直接返回。
                        // 例如[2,2,0,0],变为[0,3,0,0],然后3 > 2,进入遍历变成[0,1,1,0],因为没有大于2的元素了,所以for遍历也不会进入循环
                        // 返回 1,此时回到下面这个方法,此时 tempArray = [0,3,0,0],发现本身3大于2,所以可以得知不用进行下一个循环,因为3不符合条件,就算后面符合都没用,所以直接返回结果(不用加1,直接返回,因为本身不满足条件)
                array = tempArray;
                if(array[i] > 2){
                    return result;
                }
            }
        }
        return result+1;
    }
}


发表于 2019-03-08 10:47:19 回复(1)