1. 程式人生 > 實用技巧 >P4317 花神的數論題,關於luogu題解粉兔做法的理解

P4317 花神的數論題,關於luogu題解粉兔做法的理解

link

題意

\(\text{sum}(i)\) 表示 \(i\) 的二進位制表示中 \(1\) 的個數。給出一個正整數 \(N\) ,求 \(\prod_{i=1}^{N}\text{sum}(i)\)

思路

換一種角度看這個乘積,會發現就相當於統計出 \(1\sim N\) 中 1 的個數為 \(k\) 的數量 \(cnt_k\) ,然後 \(\prod k^{cnt_k}\) 即可。

(怎麼那麼水啊,這都什麼垃圾紫題,題白挑了)為了讓這道題更有價值,程式碼實現非常的神仙。Orz粉兔。

粉兔的程式碼看了很久才理解……luogu上至今沒有看到公開的詳解。

這裡註釋的是我認為正確的理解,若有差錯還請指正。

程式碼

#include <cstdio>
#define ll long long
const ll mod=1e7+7;
ll n,ans=1,cnt,f[50];

ll power( ll a,ll b )
{
	ll res=1;
	for ( ; b; b>>=1,a=a*a%mod )
		if ( b&1 ) res=res*a%mod;
	return res;
}

int main()
{
	scanf( "%lld",&n );

	cnt=0; f[0]=0;
	for ( int len=49; ~len; --len )
	{
		for ( int i=49; i; --i )			
			f[i]+=f[i-1];
		if ( n>>len&1 ) f[cnt]++,cnt++;			
        //cnt記錄的是除了現在這一位,之前有的1的個數,f[cnt]++表示,這一位的1產生了一種使得前面的1全部能取到的方案。
	}
	f[cnt]++;		//加上本身
//之前一直想不明白,如果這樣列舉,為什麼能直接從49開始。
//一開始的想法是預支最高位的1,這樣當前每次加一位就能取1,對應 f[i-1] 到 f[i] 的轉移
//但是這樣有個問題,就是最高位沒有1了怎麼辦,這樣預支無效,答案就會偏大
//後來發現,關鍵在外層迴圈。當位數大於二進位制下n的位數的時候,f始終為0,最後一句if 不會執行,也就不會出現上述問題。
//一旦開始累加出現了值,那麼一定就是有高位可以預支了。否則 if 中的等號不會成立。
	for ( int i=1; i<=49; ++i )
		ans=ans*power( i,f[i] )%mod;
	
	printf( "%lld",ans );
	return 0;
}