求n位二进制数(不含前导0)中所有的1出现的次数,(nleq 20)。
解
法一:递推
想到数位递推,故采取数位递推策略,显然设(g_i)为i位二进制数(不含前导0)中1出现的次数,设(f_i)为i位以内的二进制数(不含前导0)的1出现的次数,自然有
[f_i=2f_{i-1}+2^{i-1}
]
[g_i=f_{i-1}+2^{i-1}
]
边界:(f_0=g_0=0)
顺便提一下,这个递推式子,你无论用数列的知识求出通项公式,还是直接利用矩阵快速幂做到(O(log_2^n))都可以,只是此题数据范围不需要。
参考代码:
#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define ll long long
using namespace std;
ll f[21],g[21];
il void prepare();
int main(){
int lsy,n;prepare();
scanf("%d",&lsy);
while(lsy--)
scanf("%d",&n),
printf("%lld
",g[n]);
return 0;
}
il void prepare(){
for(int i(1);i<=20;++i)
f[i]=2*f[i-1]+(1<<i-1),
g[i]=(1<<i-1)+f[i-1];
}
法二:组合意义
注意到不含前导0,不妨钦定第n位填1,然后--n,这样第n+1位累加的答案就只有(2^{n-1}),然后接下来的问题就是任意个1在n个位置自由排列的1的个数,不妨枚举1的个数,再结合前面第n+1位的答案,有最终答案
[C_{n}^1+2C_{n}^2+...+nC_{n}^n+2^{n-1}
]
这个式子(O(n^2))预处理出组合数,即可直接暴力求,数据范围也支持,但是我们不止步与此,因为(C_n^1+C_n^2+...+C_n^n=2^{n}-1),而且注意到这个式子的形式,我们可以考虑等差数列乘以另一个数列的一般做法,做到(O(log_2^n)),这里就不多说了,你可以参考我的基础算法知识点小结中的基本模型的有关内容。
参考代码:
#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define ll long long
using namespace std;
ll c[21][21],ans,base[21]={1};
int main(){
int n,lsy;scanf("%d",&lsy);
for(int i(0),j;i<=20;++i)
for(j=c[i][0]=1;j<=i;++j)
c[i][j]=c[i-1][j]+c[i-1][j-1];
for(int i(1);i<=20;++i)
base[i]=base[i-1]<<1;
while(lsy--){
ans&=0,scanf("%d",&n),--n;
for(int i(1);i<=n;++i)
ans+=c[n][i]*i;
printf("%lld
",ans+base[n]);
}
return 0;
}