链接:https://www.nowcoder.com/questionTerminal/2479839aa61e44f39aa3268160650e17?orderByHotValue=1&page=1&onlyReference=false
来源:牛客网

小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


分析: 这道题我是从前往后考虑的,其实从后往前更简单哈
先将数字转化为二进制[len,...,1]
定义状态: dp[t][num] 组成第t位数字为num([t-1,1]与原数字相同)的方案只用[2^0,...,2^t]有多少种
状态转移: 第t位有两个2^t,我们有三种方案,取一个,取两个,或者不取, 然后将t位没有消去的数字转移到下一位
首先t<=3, 因为 111+111=310 高位最高取3.
然后状态转移方案受num限制, 比如num=1, 只能取一次,或者取零次

更简单的想法, 从第位往高位考虑
若n为奇数, 则最后一个1一定取, 那还剩一个1也没有啥用了 所以f(n)=f(n>>1)
    若n为偶数, 则最后一个1取零次或者取两次; f(n)=f(n>>1)+f(n>>1-1)

第一种想法的代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[75][4];
int num[75];
int cnt;
LL dfs(int len, int state) {
    if (len == 1) return state <= 2;
    if (dp[len][state] != -1) return dp[len][state];
    LL ans = 0; // 忘记初始化
    for (int i = 0; i <= 2 && i <= state; i++) {
        int k = (state - i) * 2 + num[len - 1];
        if (k <= 3)
            ans += dfs(len - 1, k);
    }
    return dp[len][state] = ans;
}
int main()
{
    LL n; cin >> n;
    if (n == 0) num[++cnt] = 0;
    while (n) {
        cnt++;
        if (n & 1) num[cnt] = 1;
        else       num[cnt] = 0;
        n = n >> 1;
    }
    memset(dp, -1, sizeof(dp));
    printf("%lld\n", dfs(cnt, num[cnt]));
    //system("pause");
    return 0;
}