0.概述
输入一个数字n,将其分解为2的幂次之和,求所有的分解方法。
1.分析
找到递推关系特别简单,但是我没找出来,所以卡住了,丢人。
为什么没找出来呢?
因为我就会最简单的DP,最简单的DP一般都是这种套路:有一个一维或者二维的DP表,我们现在想求DP[n],我们已经有DP[n-1],那么就想想怎么把这俩联系起来——我本来以为这题也能这么做,但是这题最重要的要求是分解方式不看顺序,也就是说1,1,2和1,2,1算一种,那么最简单的DP方法就用不了了。
对于有重复的DP,需要在求DP[n]的时候,把情况分开,比如说分成两类,这两类的分解方法没有重复;如果我们在最开始就把类别区分好,那么之后有重复,也没关系。这是其一。其二就是观察样例找规律:
- 1+1+1+1+1+1+1
- 1+1+1+1+1+2
- 1+1+1+2+2
- 1+1+1+4
- 1+2+2+2
- 1+2+4
这是题中给出的例子,我们可以发现,最开始的1全删掉,就是n=6时候的结果。这可很好玩,这意味着DP[n]在n为奇数的时候和n-1时候的情况一样,“全加一和全不加一的分解方法数相同”。
那么推广一下,“全乘二和全不乘二的分解方法一样”。这句话很重要。上面我们看完了奇数,然后来看偶数:对一个偶数来说,我们同样要在最开始把情况分开:分成“分解方式中有没有1”,有1,那必定有两个1;没有1,那么最小是2。
对于有两个1的情况,“全减二和全不减二的分解方法数相同”,那么DP[n]中“存在俩1”的情况数量就和DP[n-2]相同,对于不含1的情况,“全除以二和全不除以二的分解方法数相同”,那么DP[n]中“最小是2”的情况数量就和DP[n/2]相同。所以递推关系就知道了。
DP[n]=DP[n-1] or DP[n-2]+DP[n/2]
2.总结
对于需要考虑是否重复的问题,要在刚开始就把情况分开,保证“确保不同”在最初,之后就不用头痛后续是不是重复了。
PS:代码如下:
#include<iostream>
#include<vector>
#include<string>
#include<iterator>
#include<queue>
#include<list>
#include<set>
using namespace std;
int main()
{
int n;
cin >> n;
vector<int> v=vector<int>(n+1,0);
v[1] = 1;
v[2] = 2;
for (int i = 3; i <= n; ++i)
{
if (i % 2 == 1)
v[i] = v[i - 1];
else
v[i] = (v[i - 2] + v[i / 2]) % 1000000000;
}
cout<<v[n];
return 0;
}