二进制数位dp,就是把原本的数字转化成二进制而以,原来是10进制,现在是二进制来做,没有想像的那么难
不知到自己怎么相出来的。。。感觉,如果没有一个明确的思路,就算做出来了,也并不能锻炼自己的能力,因为我现在需要训练的是做题的思维方法啊!
sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,求sum(1)—sum(N) 的乘积。
首先,这道题直接做,感觉无从下手,那么我就想,怎么来转换一下,求1~n中每个数的一的个数总相乘之积,首先感觉到,每个数都会有唯一对应的1的个数,且一的个数的取值只有最多60,因为n最大 10^15, 那么我就想,如果枚举1的个数k,计算有多少个数含有k个1,(因为数位dp就是来做,有多少满足的数,且不关注数的大小)这样就转化为数位dp的模型了
另外,发现含有k个1的数个数可能非常多,快速幂搞一搞啦,
不过快速幂要注意超long long 的情况!!,因为在很多题mod比较大,mod<根号2^31,平方之后就有可能超int!!!!!
这题的关键就是发现一的个数的情况比较少可以枚举再转化为另一种情况计算其实,这题本质就是转化一下,注意在模型难以建立的情况下,通过转化,可以将题目简化,
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod=10000007;
ll n;
ll f[60][60];
int li[60];
ll dfs(int i,int state,bool limit)
{
if (state>i) return 0;
if (i==0&&state==0) return 1;
if (i==0) return 0;
if (!limit && f[i][state]!=-1) return f[i][state];
int up=limit? li[i]:1;
ll ans=0;
for (int j=0;j<=up;j++)
switch (j)
{
case 1:ans+=dfs(i-1,state-1,limit&&j==li[i]);break;
case 0:ans+=dfs(i-1,state,limit&&j==li[i]);break;
}
if (!limit) f[i][state]=ans;
return ans;
}
ll power_mod(ll a,ll k)//注意强制类型转换!!,既然mod那么大!就很有可能会超int,power_mod必须要很注意加ll
{
ll ans=1;
while (k)
{
if (k&1) ans=ans*a%mod;
a=a*a%mod;
k>>=1;
}
return ans;
}
void work(ll n)
{
memset(f,-1,sizeof(f));
int tot=0;
while (n)
{
li[++tot]=n&1;
n>>=1;
}
ll ans=1,k;
for (int i=1;i<=tot;i++)
{
k=dfs(tot,i,true);
if (k) ans=ans*power_mod(i,k)%mod;
}
printf("%lld",ans);
}
int main()
{
scanf("%lld",&n);
work(n);
return 0;
}