1. 程式人生 > 實用技巧 >Gym - 101982D Count The Bits(Dp/遞推)

Gym - 101982D Count The Bits(Dp/遞推)

Gym - 101982D Count The Bits

傳送門

題意

給定k和b,問\([0,2^n-1]\)範圍內的所有k的倍數轉化為二進位制後,總共有多少個1。

題解

維護兩個陣列\(yu[x]和sum[x]\)\(yu[x]\)表示在當前這一位之前,有多少個數%k==x,\(sum[x]\)代表在當前這一位之前,那些%k == x的數總共有多少個1。

從低位到高位遞推,假設當前已經走到第 \(i\) 位上,前 \(i-1\) 位的\(yu\)陣列和\(sum\)陣列都已經維護好了,前\(i-1\)位的答案也都計算好了,一起放在ans裡,現在考慮如何計算當前這一位可能新增的答案。

首先考慮當前這一位放0,想象如果給定一個數的二進位制形式,在他的最高位前面添一個0,不會改變這個數的大小,所以這一位為0不會對答案產生貢獻。

接下來考慮當前這一位放1,如果在這一位上1那麼之前形成的數=之前形成的數們+(\(1<<i\))。因為當前處於第 i 位,所以這一位放1是加上\(2^i\),也就是(\(1<<i\))。

因此,我先算出\((1<<i)%k=m\),然後想要知道加了這一位之後能新產生多少個k的倍數呢,答案就是\(yu[k-m]\)個,因為我當前位置的增量%k=m,那隻需要和之前產生的數中%k==k-m的數組合就能得到新的k的倍數。再考慮如何計算這些新產生的k的倍數總共有多少個1呢?

這樣考慮,這些新產生的k的倍數都是由兩部分組成的,一部分是當前位上的1,另一部分是前\(i-1\)

位裡那些%k==k-m的數的1的總數,是不是覺得這個描述特別熟悉,這就是sum陣列的定義啦,那第一部分就是有多少個%k == k-m的數,就會加上多少個1啦。

因此更新答案就是\(ans+=yu[(k-m)\% k ]+ sum[(k-m)\% k]\)

維護答案考慮完了,接下來考慮一下怎麼把當前這一位置1帶來的影響更新到yu陣列和sum陣列上。

對於每一個\(i\in[0,k)\)

都有\(yu[(i+m)\%k]+=yu[i];\)\(sum[(i+m)\%k]+=sum[i]+yu[i]\)

因為原來%k==i,現在在當前位加了1,相當於加上了一個%k ==m的數。

sum的增加和之前計算答案那裡同理。

大概就是這樣,bshd

注意點

個人覺得,此題樣例真的非常善良,因為它給了k為1的樣例,k=1的話就會要考慮很多地方要格外注意取模,這些取模可能是你考慮其他k的時候覺得沒必要的,但是在k=1的時候就顯得非常重要,所以如果他不給k=1的樣例,那非常有可能你樣例都過了然後一直wa一直wa百思不得其解然後自閉~

亂7788糟

其實我也分不清這個想法是dp還是遞推,有大佬知道的話望為我指點一下迷津,本蒟蒻不勝感激!

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e3+10;
const ll mod=1e9+9;
int yu[maxn],sum[maxn],t1[maxn],t2[maxn];
int main(){
	int k,b;scanf("%d%d",&k,&b);
	yu[0]=1;ll ans=0;
	for(int i=1;i<=b;i++)
	{
		int tt=1;
		for(int j=1;j<i;j++){
			tt=(tt*2)%k;
		}
		tt%=k;
		ans=((ans+sum[(k-tt)%k])%mod+yu[(k-tt)%k])%mod;
		for(int i=0;i<k;i++){
			t2[(i+tt)%k]=((sum[i]+yu[i])%mod+sum[(i+tt)%k])%mod;
			t1[(i+tt)%k]=(yu[i]+yu[(i+tt)%k])%mod;
		}
		for(int i=0;i<k;i++){yu[i]=t1[i];sum[i]=t2[i];}
	}
	printf("%lld\n",ans);
	return 0;
}