1. 程式人生 > 實用技巧 >花神的數論題 數位dp

花神的數論題 數位dp

題目描述

設$sum(i)$表示$i$的二進位制表示中$1$的個數。

給出一個正整數$N$,花神要問你:$\prod_{i=1}^nsum(i)$。

資料範圍

對於$100%$的資料,$N\leq10^{15}$

考慮每一位二進位制的拆解

#include <bits/stdc++.h>
#define mod (ll)10000007
using namespace std;
typedef long long ll;
const int maxn=50+5;
ll n,a[maxn],ans=1,cnt;
ll qpow(ll a,ll b) {
    ll res=1,base=a;
    
while(b) { if(b&1) res=res*base%mod; base=base*base%mod; b>>=1; } return res; } int main() { scanf("%lld",&n); for(int j=49;~j;j--) { for(int i=49;~i;i--) a[i]+=a[i-1]; if(n>>j&1) a[cnt++]++; } a[cnt]++; for(int
i=1;i<=49;i++) ans=ans*qpow(i,a[i])%mod; printf("%lld\n",ans); return 0; }
二進位制位拆解

記憶化搜尋:

  $10^{15}$分解二進位制位最多有五十位,可以列舉一共有多少位有1,50次

  $dp[i][j][k]$表示在當前無限制條件下,已經列舉到了第$i$位,有了$j$個1,求有$k$個1的方案數。

  結束條件$j=k$時才$return 1$。

#include <bits/stdc++.h>
#define mod 10000007
using namespace std;
typedef 
long long ll; int a[55]; ll n,dp[55][55][55],ans[55]; ll qpow(ll x,ll y) { ll res=1; while(y) { if(y&1) res=res*x%mod; x=x*x%mod; y>>=1; } return res; } ll dfs(int cnt,int flag,int now,int k) { if(now==k) return 1; if(!cnt) return now==k; if(!flag&&~dp[cnt][now][k]) return dp[cnt][now][k]; ll res=0; int Max=flag?a[cnt]:1; for(int i=0;i<=Max;i++) { res+=dfs(cnt-1,flag&&(i==Max),now+(i==1),k); } if(!flag) return dp[cnt][now][k]=res; return res; } ll query(ll x) { int cnt=0; ll res=1; while(x) { a[++cnt]=x&1; x>>=1; } for(int k=1;k<=50;k++) { for(int i=0;i<=a[cnt];i++) { ans[k]+=dfs(cnt-1,(i==a[cnt]),(i==1),k); } } for(int i=1;i<=50;i++) { res*=qpow(i,ans[i]); if(res>=mod) res%=mod; } return res; } int main() { memset(dp,-1,sizeof(dp)); scanf("%lld",&n); printf("%lld\n",query(n)); return 0; }
dfs版