Acwing 1081. 度的数量(以及本人对数位dp的浅薄理解)
題意:
求給定區(qū)間 [X,Y] 中滿足下列條件的整數(shù)個(gè)數(shù):這個(gè)數(shù)恰好等于 K 個(gè)互不相等的 B 的整數(shù)次冪之和。
題解:
數(shù)位DP
技巧1:[X,Y]=>f(Y)-f(X-1)
技巧2:用樹的方式來考慮。
在本題中,題意是問[X,Y]中的數(shù) 轉(zhuǎn)發(fā)成B進(jìn)制,B進(jìn)制有K位是1
比如樣例中:17 ,k=2,B=2
就是把17轉(zhuǎn)成二進(jìn)制,看二進(jìn)制中是否有K個(gè)1
17的二進(jìn)制是:10001,說明17=24+20,符合要求
我們現(xiàn)在想具體做法:我們利用前綴和的方式,想法1~n之間符合要求的個(gè)數(shù),這樣就可以方便求出任意區(qū)間的數(shù)量。
對(duì)于一個(gè)N位數(shù),我們將N的各位拆成an-1到a0(從高到底),然后我們用樹的形式來考慮,對(duì)于最高位an-1,他可以是an-1或者是0到an-1-1之間的數(shù),如果是0到an-1-1之間的數(shù),我們具體討論,如果是1,說明剩下位置是需要k-1個(gè)1,如果是非1,剩下位置只需要k個(gè)1,直接用組合數(shù)就可以表示(如圖),而對(duì)于第an-1位是an-1的話我們可以繼續(xù)分解
參考題解(下圖來源)
談?wù)勎覍?duì)數(shù)位dp的理解,為什么要拆分成左右兩側(cè),左側(cè)是0 ~ an-1,右側(cè)為an,我的理解:數(shù)位dp就是一種優(yōu)化的統(tǒng)計(jì)的方式,因?yàn)槲覀冇?jì)算的是1~n中所有滿足答案的情況,那統(tǒng)計(jì)的x一定要小于等于n,我們將n拆成各位,我們從高位向低位枚舉時(shí),假如說n是7854,我們將n拆成開,就是4位,從高到低分別是7,8,5,4,我們?cè)诿杜e第一位(最高位時(shí)),最高位如果我們枚舉0 ~ 6,那么后三位可以是任意數(shù),因?yàn)榭隙ū?854小,對(duì)不對(duì),然后統(tǒng)計(jì)這部分答案。如果我們枚舉是7,那你后三位不能隨便枚舉,如果你第二位枚舉9,那就超出范圍了,為了避免超出范圍,如果我們第一位枚舉7,那我們就看第二位,第二位的上限是8,那我們就枚舉 0 ~ 7 ,這樣后兩位就可以隨便枚舉,統(tǒng)計(jì)答案,然后我們第二位取上限8,再往后推一位。。。依次類推,終點(diǎn)就是我們推到7854,就是n本身(我們?cè)谕频倪^程中是加了判斷條件,如果不滿足條件就break,如果能推到最后,說明最終這個(gè)n也是滿足條件的)。這樣說不知道能不能理解
代碼:
#include<bits/stdc++.h> using namespace std; const int N = 35; //位數(shù)int f[N][N];// f[a][b]表示從a個(gè)數(shù)中選b個(gè)數(shù)的方案數(shù),即組合數(shù)int K, B; //K是能用的1的個(gè)數(shù),B是B進(jìn)制//求組合數(shù):預(yù)處理 void init(){for(int i=0; i< N ;i ++)for(int j =0; j<= i ;j++)if(!j) f[i][j] =1;else f[i][j] =f[i-1][j] +f[i-1][j-1]; }//求區(qū)間[0,n]中的 “滿足條件的數(shù)” 的個(gè)數(shù)//“滿足條件的數(shù)”是指:一個(gè)數(shù)的B進(jìn)制表示,其中有K位是1、其他位全是0 int dp(int n){if(n == 0) return 0; //如果上界n是0,直接就是0種vector<int> nums; //存放n在B進(jìn)制下的每一位//把n在B進(jìn)制下的每一位單獨(dú)拿出來while(n) nums.push_back( n% B) , n/= B;int res = 0;//答案:[0,n]中共有多少個(gè)合法的數(shù)//last在數(shù)位dp中存的是:右邊分支往下走的時(shí)候保存前面的信息 //遍歷當(dāng)前位的時(shí)候,記錄之前那些位已經(jīng)占用多少個(gè)1,那么當(dāng)前還能用的1的個(gè)數(shù)就是K-lastint last = 0; //從最高位開始遍歷每一位for(int i = nums.size()-1; i>= 0; i--){int x = nums[i]; //取當(dāng)前位上的數(shù)if(x>0){ //只有x>0的時(shí)候才可以討論左右分支//當(dāng)前位填0,從剩下的所有位(共有i位)中選K-last個(gè)數(shù)。//對(duì)應(yīng)于:左分支中0的情況,合法res += f[i][ K -last];//i個(gè)數(shù)中選K-last個(gè)數(shù)的組合數(shù)是多少,選出來這些位填1,其他位填0if(x > 1){//當(dāng)前位填1,從剩下的所有位(共有i位)中選K-last-1個(gè)數(shù)。//對(duì)應(yīng)于:左分支中填1的情況,合法if(K - last -1 >= 0) res += f[i][K -last -1];//i個(gè)數(shù)中選K-last-1個(gè)數(shù)填1的組合數(shù)是多少//對(duì)應(yīng)于:左分支中其他情況(填大于1的數(shù))和此時(shí)右分支的情況(右側(cè)此時(shí)也>1),不合法!!!直接break。break;}//上面統(tǒng)計(jì)完了**左分支**的所有情況,和右分支大于1的情況,//這個(gè)else 是x==1,//對(duì)應(yīng)于:右分支為1的情況,即限定值為1的情況,也就是左分支只能取0//此時(shí)的處理是,直接放到下一位來處理//只不過下一位可使用的1的個(gè)數(shù)會(huì)少1,體現(xiàn)在代碼上是last+1else if(x==1)//相當(dāng)于限定值為1 {last ++;//如果已經(jīng)填的個(gè)數(shù)last > 需要填的個(gè)數(shù)K,不合法breakif(last > K) break;}}//上面處理完了這棵樹的**所有**左分支,就剩下最后一種右分支的情況// 也就是遍歷到最后1位,在vector中就是下標(biāo)為0的地方:i==0;// 并且最后1位取0,才算作一種情況res++。因?yàn)樽詈?位不為0的話,已經(jīng)被上面的ifelse處理了。if(i==0 && last == K) res++; }return res; }int main(){init();int l,r;cin >> l >> r >> K >>B;cout<< dp(r) - dp(l-1) <<endl; }總結(jié)
以上是生活随笔為你收集整理的Acwing 1081. 度的数量(以及本人对数位dp的浅薄理解)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Acwing 1083. Windy数
- 下一篇: Acwing 1084. 数字游戏 II