花神的数论题(这题...哎。数位dp咋就这么 not naive 呢)

题意简介

没什么好说,就是让你求出 1 ~ n 之间每个数转化为二进制后 '1' 的个数,然后乘起来输出积

题目分析

emmmm.... 两种解法(同是 $O(\log^2 N)$ 算法啊...)。

  1. 组合数直接艹。(数据范围8e9 能过,其实这个东西你搞一搞 __int128 什么的再找个大质数也许也能过去啦)
  2. 老老实实数位dp。(可以AC)

于是翻车了,感觉非常难受,为什么这题数据范围就是要卡组合数呢...(mmp 数据范围改到1e10 以下估计就能过去了...)

算法实现

1. 组合数 

组合数非常好做,只需要想想第 i 位为 1 的位上后面那一堆数字里面挑出 j 个数的方案就好了,注意加上 cnt ,然后最后快速幂累加一下答案,分分钟搞定

然后这里用了逆元处理组合数要预处理的阶乘,如果你还不会线性求逆元的话可以点这里

2.数位dp

mmp...不就是枚举 1 的个数然后记忆化深搜么?hem...听起来都没有组合数高级

代码实现

1.组合数

 1 //by Judge (8e9范围)
 2 #include<iostream>
 3 #include<cstdio>
 4 #define ll unsigned long long
 5 using namespace std;
 6 const int M=55;
 7 const ll mod=1e7+7;
 8 const ll mo=1e9+7;
 9 ll n,len,cnt,ans=1;
10 ll fac[M],finv[M];
11 ll d[M],num[M];
12 inline void prep(){  //线性筛逆元模板?
13     fac[0]=finv[0]=finv[1]=1;
14     for(int i=1;i<=50;++i)
15         fac[i]=(fac[i-1]*i)%mo;
16     for(int i=2;i<=50;++i)
17         finv[i]=finv[mo%i]*(mo-mo/i)%mo;
18     for(int i=2;i<=50;++i)
19         finv[i]=finv[i]*finv[i-1]%mo;
20 }
21 inline ll quick_pow(ll x,ll p,ll ans=1){ //快速幂模板?
22     while(p){
23         if(p&1) ans=ans*x%mod;
24         x=x*x%mod, p>>=1;
25     } return ans;
26 }
27 inline ll C(ll n,ll m){  //组合数模板?
28     return fac[n]*finv[m]%mo*finv[n-m]%mo;
29 }
30 signed main(){
31     cin>>n,prep();
32     while(n) d[++len]=n&1,n>>=1;  //转化二进制
33     for(ll i=len,j;i;--i) if(d[i]){
34         for(j=1;j<i;++j) //组合数就是随便乱艹的算法
35             num[cnt+j]+=C(i-1,j);
36         ++num[++cnt];
37     }
38     for(ll i=1;i<=len;++i) //直接累乘就好
39         ans=ans*quick_pow(i,num[i])%mod;
40     cout<<ans<<endl; return 0;
41 }

2.数位dp

 1 //by Judge
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 #define ll long long
 6 using namespace std;
 7 const int M=55;
 8 const int mod=1e7+7;
 9 int cnt,x[M];
10 ll n,f[M][2][M][M],num[M];
11 inline ll quick_pow(ll x,ll p,ll ans=1){
12     while(p) (p&1) && (ans=ans*x%mod),x=x*x%mod, p>>=1;
13     return ans;
14 }
15 ll dp(int cur,int up,int tmp,int d){ //记忆化深搜,log^2 n 无压力
16     if(!cur) return tmp==d; //特判直接返回
17     if(~f[cur][up][tmp][d]) return f[cur][up][tmp][d]; // 已记忆,直接返回
18     int lim=up?x[cur]:1;
19     ll res=0;
20     for(int i=0;i<=lim;++i) //继续深搜
21         res+=dp(cur-1,up&&i==lim,tmp+(i==1),d);
22     return f[cur][up][tmp][d]=res; //记忆化
23 }
24 ll solv(){
25     while(n) x[++cnt]=n&1,n>>=1; //同上转化
26     for(int i=1;i<=50;++i)  //枚举要放入的 1 的位数
27         memset(f,-1,sizeof(f)),
28         num[i]=dp(cnt,1,0,i);
29     ll res=1;
30     for(int i=1;i<=50;++i) //累乘进答案
31         res=res*quick_pow(i,num[i])%mod;
32     return res;
33 }
34 signed main(){ cin>>n,cout<<solv()<<endl; return 0; }

猜你喜欢

转载自www.cnblogs.com/Judge/p/9540939.html
今日推荐