1. 程式人生 > 其它 >7月6日(淼)——數位dp

7月6日(淼)——數位dp

前言

但拎出來寫部落格的原因如標題所示。實際上就是想水部落格。以後這個“(淼)”系列會有心情去更的。
所以以後這個系列的標題名暫時定為《x月x日(淼)——(此處填水的內容)》
數位dp我基本都寫記搜。目的是學會怎麼使用那個模板。
題外話:求助!HDU怎麼進去啊,我想測題目!

題目選做

本來想叫題目選講的,但是想了想,這些題目基本都是看了別人的題解,模仿寫出來的,這篇部落格的目的也是加深自己的理解。所以還是叫“題目選做”比較好。
我一直很贊同奧術師們(出自《奧術神座》)把自己的一些想法和知識寫成論文這種形式,這樣某種程度來說,確實能很有收穫,雖說沒有所謂的現實反饋那麼誇張……
那麼話歸正題:

正文

HDU 不要62

題目描述:

給定一個區間\([L,R]\) ,求這個區間中有多少個不含\(4\)\(62\)的數

資料範圍:

\(0≤L≤R<10^6\)

題目分析:

寫動態規劃最關鍵的是設計狀態和找轉移(我覺得),所以先考慮怎麼設計狀態。
數位dp往往用字首和的方法把對\([L,R]\)的詢問改為對\([0,R]-[0,L-1]\)的詢問。所以設\(dp\)陣列的第一維是查到了數的第幾位。(暫定從高向低查)
題目中的不含\(4\)可以用簡單的在搜下一位時,就判斷此為是否是\(4\),如果是,就\(continue\)這種方法來剪掉。
所以我們\(dp\)陣列的第二位就記,這一位是否是\(6\)

,如果是,下一位就需要判斷是否是\(2\).
關於\(limit\):
\(limit\)是最高位限制,來表示下一位應該取進位制下的最大值還取原數限制下的最大值。
\(limit=1\)表示這一位現在取了原數限制下的最大值。
比方說原數是:\(123456\)
現在填到了:\(123xxx\)
此時下一位受原數限制,只能最大填到\(4\)

如果現在填到了:\(1233xx\)
那麼下一位受進位制的限制,最大填到\(9\)

(此處的‘x’指還未填的數)

套用板子有以下程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=63;//正常來說,一個數的位數不會超過63 
int a[N],dp[N][2],l,r;
int dfs(int len,bool pre,bool limit){//分別代表位數、是否是6(是為1)、最高位限制 
	if(len==0) return 1;
	if(!limit && dp[len][pre]!=-1) return dp[len][pre];
	//這裡,如果這個數搜過了,且它的值可以被更新,它在這步return了 
	int qwq=0,res=limit?a[len]:9;//所以進入這步的都是沒算過或不能記錄的 
	for(int i=0;i<=res;i++){
		if(i==4) continue;//如題,如果是4是不可能的
		if(pre==1&&(i==2)) continue;//如題,如果兩位是62,也不行 
		bool n_limit= limit && (i==res);
		//如果limit=1,說明上一位到了原數限制的最高位,此時res=這一位原數限制的最高位
		//那麼對於下一位,如果這一位的i=res,下一位的limit就等於1
		bool n_pre=(i==6)?1:0;
		qwq+=dfs(len-1,n_pre,n_limit); 
	} 
	return limit?qwq:dp[len][pre]=qwq;//limit=1時,dp是不能被更新的 
}
int work(int x){
	int len=0;
	memset(dp,-1,sizeof(dp));
	while(x)a[++len]=x%10,x/=10;
	return dfs(len,0,1);
}
int main(){
	cin>>l>>r;
	cout<<work(r)-work(l-1);
	return 0;
}

之前看了題解寫了一遍,現在終於自己也能寫出來了……

座右銘:我從來沒有見過這樣陰鬱而又光明的日子。