數字dp踩坑

2022-05-24 16:57:10 字數 4849 閱讀 6069

數字dp是什麼?以前總覺得這個概念很高大上,最近閒的沒事,學了一下發現確實挺神奇的。

hdu 2089 "不要62"

乙個數字,如果包含'4'或者'62',它是不吉利的。給定m,n,0這題的資料範圍比較小,只有1e6,理論上暴力也是可以解的。但數字dp的題目資料範圍通常很大,往往達到1e18甚至更大,暴力法o(n)顯然會tle。這個時候需要一種時間複雜度近似o(logn)的演算法。仔細思考一下,其實可以使用排除法。從高位到低位依次排除0~1e6中不符合條件的數。

舉個例子,1~999999中不包含4的數,步驟如下:

1.先排除6位數中最高位是4的數,即400000~499999,只需要判斷最高位,就排除了10萬個數。

2.接著排除次高位是4的數(此時預設最高位不是4),比如最高位是1時可以排除140000149999,共1萬個。注意,首位**可以是0**,此時相當於考慮000000099999(初學者可能有點不理解,看完後面一些不考慮前導0的題就懂了)。

3.同理,繼續排除4位數,3位數,直到結束。

數字dp是對數字的「位」進行的和計數有關的dp,數的每乙個位稱為數字,乙個數有個位、十位、百位、千位……數字dp用來解決和數字操作有關的問題,比如某區間內數字和,特定數字問題等。這些問題的數字範圍通常很大,無法暴力解決,必須用接近o(logn)的演算法。通過dp對「數字」操作,記錄算過的區間的狀態,用於後續計算快速篩選數字。

那麼這道題用數字dp怎麼實現呢?數字dp一般有兩種方法,一種是dp預處理亂搞,另一種是記憶化dfs。前者不適合模板化,而後者效率高,模板易記,可以快速上手。

#include using namespace std;

typedef long long ll;

ll dp[20][20];

int a[20];

ll n,m;

// pos:當前到第幾位 pre:上一位數字是什麼 lead:是否有前導0 limit:是否有上界限制 (引數通常有pos和limit,是否有lead看題目要求,此外還有可能增加其他引數)

ll dfs(int pos,int pre,bool lead,bool limit)

}if(!lead && !limit) dp[pos][pre] = ans;

return ans;

}ll solve(ll x)

memset(dp,-1,sizeof(dp)); //初始化dp陣列

return dfs(len,0,true,true); //從高位向低位列舉

}int main()

這裡解答幾個初學者常見的疑問

下面我們通過模板來秒掉這道題目吧

題目鏈結

dp[pos][sta]表示第pos位前一位是6(sta = 1),或者不是6(sta = 0)時滿足條件的數的數目,這裡由於數的前一位是否是6會對答案構成影響,所以只需要在統計時把答案分成兩類。

#include using namespace std;

typedef long long ll;

int n,m;

ll dp[20][2];

int a[20];

int dfs(int pos,int pre,int sta,bool limit)

if(!limit) dp[pos][sta] = sum;

return sum;

}int solve(int x)

return dfs(len,-1,0,true);

}int main()

return 0;

}

題目鏈結

這題定義了乙個叫"k好數"的概念,即該數在k進製下任意相鄰兩位的數不能相鄰(即相差不能等於1),然後要統計l位k進製中k好數的數目。這道題的坑點在於要考慮前導0的影響,比如4進製下的兩位數,11,20是k好數,但是32不是k好數。但如果我們求的是4進製下的一位數,問題就來了,如果沒有lead這個引數,1這個數就不會被統計,因為傳參時預設前一位是0,而0和1相鄰,這個時候只需要加乙個判斷即可。

#include using namespace std;

typedef long long ll;

ll mod = 1e9 + 7;

ll dp[105][105];

int k,l;

ll dfs(int pos,int pre,bool lead,bool limit)

}if(!lead && !limit) dp[pos][pre] = ans;

return ans;

}ll solve(int len)

int main()

題目鏈結

這題定義了乙個叫b數的東西,這個數含"13"這個串並且可以被13整除。難點在於怎麼儲存被13整除這個狀態,其實很簡單,再加乙個引數tot記錄餘數,每次列舉%一下13,當pos=0時判斷即可。

#include using namespace std;

typedef long long ll;

ll dp[15][10][13][2];

int a[15];

ll n;

ll dfs(int pos,int pre,int tot,int ok,bool limit)

if(!limit) dp[pos][pre][tot][ok] = ans;

return ans;

}ll solve(ll x)

return dfs(len,0,0,false,true);

}int main()

題目鏈結

這道題挺有意思的,要統計的是每個數二進位制下1的個數sum(i),然後求sum(1)到sum(n)的累乘。

我們要轉換一下思維,n上限是1e18,意味著這個數的二進位制最多有50位,即最多就50個1,我們分別統計二進位制下有乙個1,兩個1,三個1……五十個1的數的個數,然後用快速冪進行優化,問題就迎刃而解了。

#include using namespace std;

typedef long long ll;

const ll mod = 1e7 + 7;

ll dp[51][51][51];

int a[51];

ll ans[51];

ll n;

ll poww(ll x,ll y,ll p)

return ans;

}//位置 當前統計到的1的個數 目標要統計的1的個數 上界限制

ll dfs(int pos,int tmp,int tot,bool limit)

if(!limit) dp[pos][tmp][tot] = sum;

return sum;

}ll solve(ll x)

memset(dp,-1,sizeof(dp));

//這裡是乙個很巧妙的優化 分別統計二進位制中1的個數為i的數的個數,然後快速冪優化

for(int i = 1;i <= len;i++)

return sum;

}int main()

題目鏈結

這道題大意是求某區間內數字0-9出現的次數。我大概的想法是,分別統計區間內存在1個1,2個1,3個1……的數的個數,存在1個2,2個2,……,1個9,2個9,……的數的個數,然後求累加,當然題解有更好的方法,以下僅供參考(注意前導0的處理,否則統計0的時候會出錯)

#include using namespace std;

typedef long long ll;

ll dp[15][15][15];

int a[15];

ll ans[10][15];

ll total[10];

ll n,m;

int num;

//前導0很關鍵!!

ll dfs(int pos,int now,int tot,bool lead,bool limit)

if(!limit && !lead) dp[pos][now][tot] = sum;

return sum;

}ll solve(ll x,bool ok)

memset(dp,-1,sizeof(dp));

memset(ans,0,sizeof(ans));

for(int i = 0;i < 10;i++)

}}int main()

題目鏈結

題目的大意是求l-r區間內每個數的各位數字和,假設這個數共len位,那麼它的各位數字之和不會超過9*len(因為每位數字最大是9),我們只需要從小到大列舉統計即可,本質思想和上面的題目是一樣的。

#include using namespace std;

typedef long long ll;

const ll mod = 1e9 + 7;

int t;

ll n,m;

ll dp[20][180][180],ans[180];

int a[20];

//位置 當前的各位和 目標和 上界限制

ll dfs(int pos,int now,int tot,bool limit)

if(!limit) dp[pos][now][tot] = sum;

return sum;

}ll solve(ll x)

//統計各位和為1-9*len的所有情況

for(int i = 1;i <= 9 * len;i++)

return sum;

}int main()

return 0;

}

蒟蒻的第一篇blog,有錯還請各位神犇指出~~

systemtap embedded C 踩坑筆記

官方文件 systemtap的embedded c中,不能 include 也不能用printf和print。那怎麼列印呢?用stap printf。用法與printf一樣。還可以訪問cript中的全域性變數。官方文件中的示例 global var global var2 100 function ...

Aggregation MongoDB踩坑記錄

對某些篩選條件進行分頁查詢,開始每一頁的有效data都不足pagesize,最後發現,aggregation 的pipeline是有先後順序的。錯誤 agg aggregation.newaggregation aggregation.skip curpage 1 pagesize aggregat...

feign踩坑 通過Feign上傳檔案(踩坑)

引入依賴 org.springframework.cloud spring cloud starter openfeign 服務提供者 restcontroller public inte ce fileuploadservice commonresultuploadfile requestpart...