1. 程式人生 > 實用技巧 >P4127 [AHOI2009]同類分佈

P4127 [AHOI2009]同類分佈

原題連結https://www.luogu.com.cn/problem/P4127

題解

淺談數位dp

昨天通過網課複習了一下數位dp,然後來做幾道數位dp的題來練練手。

經典的數位dp 是要求統計符合限制的數字的個數

一般的形式是:求區間 [ n , m ] 滿足限制 f ( 1 ) 、f ( 2 ) 、f ( 3 ) 等等的數字的數量是多少。條件 f ( i ) 一般與數的大小無關,而與數的組成有關。

善用不同進位制來處理,一般問題都是十進位制和二進位制的數位dp。

數位dp 的部分一般都是很套路的,但是有些題目在數位dp 外面套了一個華麗的外衣,有時我們難以看出來。

數位dp 一般用記憶化搜尋來實現。

數位dp 的時間複雜度與位數有關。

回到本題

一個很自然的想法是在搜尋過程中維護當前已經選的數的大小和各個位的數字之和,當列舉完所有位之後判斷模數是否為 0 即可;

想法固然不錯,但是數實在太大,沒法記憶化,會超時,不可取!

考慮到我們其實並不關心這個數本身是幾,只需關心這個數模各個位的數字之和是幾,而模數 ( 各個位的數字之和 ) 是小於 9*18 的,所以餘數也是小於 9*18 的,可行。

但是在選數的過程中,模數也是變化的,所以我們無法維護餘數的大小,怎麼辦呢?

我們讓模數不變!

我們可以去列舉 [ 1 , x ] 範圍內的所有數的最大的各個位的數字之和是幾,等到選完所有位之後,再判斷你選完的數的各個位的數字之和與你一開始列舉的是否相同即可,不相同即不合法狀態;

剪枝

想出正解的你開開心心地把程式碼交上去,然後TLE了......

考慮怎麼剪枝,記憶化我們都加上了,看來只能在各個位的數字之和上面動點手腳了qwq。

考慮到判斷一個狀態合法的前提是你選出的各個位的數字之和要等於你一開始列舉的那個數,由此我們可以想到兩個可行性剪枝:

①. 如果當前你選的各個位的數字之和 sum 已經大於了你一開始列舉的那個數,那麼之後的狀態一定是不合法的,直接返回 0 即可;

②. 如果你剩下的所有位都選了 9,但是加起來的 sum 還是比你一開始列舉的那個數小,那麼之和再怎麼選都是不合法的,直接返回 0 即可;

Code:

#include<iostream>
#include
<cstdio> #include<cstring> using namespace std; long long l,r; int c[100]; long long dp[100][200][200]; long long dfs(int k,int Mod,int mod,int sum,bool limit) //當前正在選第k位,模數是Mod,餘數是mod,選的所有位的數字之和為sum,是否頂上界 { if(sum>Mod||sum+9*(k+1)<Mod) return 0; //如果當前選的各個數的數字之和已經大於Mod了,或者之後的位數都選9還是小於Mod,那麼不用搜了,肯定是不合法的 if(k<0) //列舉完所有位數了 { if(sum==Mod&&mod==0) return 1; //必須要保證你選的各個位的數字之和等於Mod,且餘數mod==0才是符合要求的狀態 return 0; } if(!limit&&dp[k][mod][sum]!=-1) return dp[k][mod][sum]; //記憶化 int up=9; if(limit) up=c[k]; //up是當前位所能填的最大數字 long long ans=0; for(int i=0;i<=up;i++) { ans+=dfs(k-1,Mod,(mod*10+i)%Mod,sum+i,limit&&i==up);//轉移到下一位 } if(!limit) dp[k][mod][sum]=ans; return ans; } long long solve(long long x) //求[1,x]內符合條件的數的個數 { memset(c,0,sizeof(c)); int len=0; while(x) //將x一位一位地拆開 { c[len++]=x%10; x/=10; } long long ans=0; for(int i=1;i<=9*len;i++) //列舉所有數的各位數字之和,最大是9*len { memset(dp,-1,sizeof(dp)); //注意每次搜尋前都要memset ans+=dfs(len-1,i,0,0,1); //現在在列舉第len-1位,模數是i,餘數是0,選的各個位的數字之和為0,頂上界 } return ans; } int main() { scanf("%lld %lld",&l,&r); printf("%lld\n",solve(r)-solve(l-1)); //字首和的思想 return 0; }

總結

10 進位制數位dp 的基本最簡單的形式。

記憶化搜尋處理數位dp 的程式碼實現,數位dp 一般都用記憶化搜尋來做。

考察思維的數位dp 往往會和其他如列舉演算法結合,或作為原問題的子問題。

除了十進位制,二進位制的數位dp 也是常見的,此外 K 進位制的也是可以的。