1. 程式人生 > 其它 >【ybtoj】【數位dp專題】

【ybtoj】【數位dp專題】

前言

假期最後兩天不想做什麼太難的,就把數位DP開了吧!正好填之前挖的坑

數位DP看起來貌似都比較裸...而且題目簡短,注意一下程式碼的細節就好

本篇記錄裡全部使用記憶化搜尋

目錄

  • A. 【例題1】B數計數

  • B. 【例題2】區間圓數

  • C. 【例題3】數字計數

  • D. 【例題4】數字整除

  • F. 1.幸運數字

題解

A. 【例題1】B數計數

分析:

此題是第一道ybt的數位dp題,引入一些模板的寫法

對於所有求1~n,L~R的 xx數 個數的,一般都是從高位到低位搜尋,而且記錄一個 ok 變數來表示當前數位可不可以隨便填數字(每一位數字填完最後不能比 n 大)

由於ok 變數不需要在 dp 數組裡記錄,可以直接傳參到記憶化搜尋裡,但是ok==0和ok==1時候 dp 陣列的記憶化值是不一樣的,所以規定只記憶化ok==1(即可以隨便填)時的 dp 值,這是因為ok==0的情況很少,所以不用記憶化問題也不大(ps:測試時發現列舉 i 的時候倒序列舉也可以使記憶化不重複,但是過於玄學我就不提了)

實際上,應該也可以在 dp 陣列中記錄 ok ,理論上空間會變大一倍(ok取值0,1),但是搜尋部分會快一點點

那麼迴歸本題,設計dp狀態為 dp[pos][res][op]

pos表示第幾位,res表示餘數,op表示13數出現的狀態(op==0沒出現過,op==1上一位是1而且之前沒出現過完整的13,op==2表示出現過13),maxn表示當前最大能填的數字,ok表示當前這位是否可以隨便填
(一開始我想用check表示是否出現過13,其實不需要,可以用op代替)

程式碼:

A. 【例題1】B數計數
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
int n,dp[11][14][3],dig[11],m,mi[11];
void init()
{
	//memset(dp,-1,sizeof(dp));
	//memset(dig,0,sizeof(dig));
	m=0;
	while(n)
	{
		int x=n%10;
		dig[++m]=x;
		n/=10;
	}
}
//可以有前導零,不用記錄zero 
//pos表示第幾位,res表示餘數,op表示13數出現的狀態,maxn表示當前最大能填的數字
//(一開始我想用check表示是否出現過13,其實不需要,可以用op代替)
//ok表示當前這位是否可以隨便填 
int solve(int pos,int res,int op,bool ok)
{
	if(pos==0) return dp[pos][res][op]=(op==2&&res==0);
	if(dp[pos][res][op]!=-1&&ok) return dp[pos][res][op];
	int ret=0,maxn=9;
	if(!ok) maxn=dig[pos];
	for(int i=maxn;i>=0;i--)
	{
		int tmp=(res+mi[pos]*i)%13;
		//分類討論當前填的數字大小 
		if(i<maxn)
		{
			//if(check) ret+=solve(pos-1,tmp,op,1,1);
			if(op==2) {ret+=solve(pos-1,tmp,2,1);continue;}
			if(i==1) ret+=solve(pos-1,tmp,1,1); 
			else if(i==3&&op==1) ret+=solve(pos-1,tmp,2,1);
			else ret+=solve(pos-1,tmp,0,1);
		}
		else 
		{
			if(op==2) {ret+=solve(pos-1,tmp,2,ok);continue;}
			if(i==1) ret+=solve(pos-1,tmp,1,ok); 
			else if(i==3&&op==1) ret+=solve(pos-1,tmp,2,ok);
			else ret+=solve(pos-1,tmp,0,ok);
		}
	}
//	printf("dp[%d][%d][%d]=%d\n",pos,res,op,ret);
	if(ok) dp[pos][res][op]=ret;
	return ret;
	
}
int main()
{
	mi[1]=1;
	for(int i=2;i<=10;i++) mi[i]=mi[i-1]*10;
	while(scanf("%d",&n)!=EOF)
	{
		init();
		printf("%d\n",solve(m,0,0,0)); 
	}
	return 0;
}
/*
input:
131312
131313
13333
13332
13338
output:
550 
551
60
60 
61
*/

B. 【例題2】區間圓數

分析: