1. 程式人生 > >bzoj3209 花神的數論題——數位dp

bzoj3209 花神的數論題——數位dp

嘗試 for 其他 space 多個 多少 部分 不同 正整數

題目大意:

花神的題目是這樣的 設 sum(i) 表示 i 的二進制表示中 1 的個數。給出一個正整數 N ,花神要問你 派(Sum(i)),也就是 sum(1)—sum(N) 的乘積。

要對10000007(非質數)取模

n<=10^15

分析:

O(nlogn)暴力顯然可以想出來。顯然會tle

這是從1~n一個一個枚舉並變成二進制算的,但是我們是否可以向普通的數位dp,一次性枚舉許多個數呢?

二進制的n,大概最多50位。例如21=10101.它顯然可以拆成二進制下的10000+100+1

那麽,我們是否可以嘗試著去先算出來1~10..0的sum乘積?

假設這是一個n位數,也就是有n-1個零

考慮只有一個1的數字個數,C(n,1),即,在n個位置上,取1個位置變成1的方案數。那麽,sum[i]=1的數的貢獻就是1^c(n,1)

只有二個1,C(n-1,2),註意,是n-1位,因為除了特殊的10..000之外,其他的數都只有n-1個位置可放1。同理,sum[i]=2的數的貢獻就是2^c(n-1,2),因為本身就是連乘嘛,交換律結合律就先把不同個數的數所做的貢獻乘起來。

三個1同理。

……

以21=10101為例,這樣我們可以切掉n的第一個10000以下的方案。

現在我們要處理10001~10100的方案數,我們仍然可以利用剛才處理100的思路,

只是,放一個1在最後的三位,必然每次都要加上之前已經有過的那一個1(10000),所以,是2^c(3,1)

2個1同理,是3^c(2,2)

這樣,就解決問題了。

代碼:

#include<bits/stdc++.h>
using
namespace std; typedef long long ll; const int mod=10000007; ll ans=1; ll sum; ll wei; ll c[52][52]; ll n; ll qm(ll x,ll y) { ll ret=1,base=x; while(y) { if(y&1) ret=(ret*base)%mod; base=(base*base)%mod; y>>=1; } return ret; } int main() { c[0][0]=1
; for(int i=1;i<=50;i++) { c[i][0]=1; for(int j=1;j<=50;j++) c[i][j]=c[i-1][j-1]+c[i-1][j]; }//直接打表就好, //註意,組合數將作為指數部分,不能取模 C(50,25)long long也開的下, scanf("%lld",&n); wei=0;sum=0;//sum,已經處理出來的之前的1的個數 for(wei=50;wei>=1;wei--)//按位枚舉 { if(n&((ll)1<<wei-1)) { ans=(ans*qm(sum+1,wei))%mod;//處理1個的特殊情況 for(int k=sum+2,s=2;k<=sum+wei-1;k++,s++) { ans=(ans*qm(k,c[wei-1][s]))%mod; } sum++; } } printf("%lld",ans); return 0; }

總結:

數位dp的最初思想的來源,就是利用整千,整萬,整十萬的整齊特殊性質,可以利用可以想到的數學方法,對枚舉進行大幅的簡化,直接減少到O(logn)

這個題其實算是數位dp的裸題,還是比較常規的。

對於其他的符合規定的第k小的數(啟示錄),是先預處理整位的情況,再按位枚舉,考慮每個數能填幾,從而類似康拓展開,找到第k小的數。

或者[l,r]區間內多少個滿足限制的數,前綴和思想,求l-1以內,再求r以內做差就好。

bzoj3209 花神的數論題——數位dp