1. 程式人生 > 實用技巧 >ZJOI2010 數字計數【數位dp】

ZJOI2010 數字計數【數位dp】

題目連結

題目解析

稍微有點難度的數位\(dp\)

這道題的話,你發現前導零需要特判一下,不然你就會把前導零數到\(0\)的個數裡面去。

然後就是狀態定義的不同 這道題把計數\(sum\)也放在記憶化數組裡面了,理由詳見程式碼註釋。


►Code View

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 15
#define MOD 1000000007
#define INF 0x3f3f3f3f3f3f3f3f
#define LL long long
LL rd()
{
	LL x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	return f*x;
}
int d[N],n=0;
LL f[N][N][2][2];//f[i][j][m][p] 當前位數到i 當前數位dig有j個 後面兩維是最高位和前導零 一共有多少個dig 
/*
關於狀態為什麼要記錄當前狀態表示出來的數的dig個數
可以想一下如果去掉會怎麼樣 或者是想一下不同的sum之間有無區別 因為是記憶化嘛 
發現是有區別的 因為在dp的時候 這個狀態會拿去更新別人
而sum不同代表你前面枚到的數不一樣 那麼你拿去更新別人的時候 對別人的sum的貢獻不一樣 
如果全部混為一談 答案會算多

而dp值是相當於整個dfs樹的子樹答案和 也就是做到這一位的答案總數 
*/ 
LL dfs(int dig,int p,int ld,int s,int sum)//有前導零ld=1
{
	if(p==0) return sum;
	if(f[p][sum][ld][s]!=-1) return f[p][sum][ld][s];
	LL res=0;
	int lim=9;
	if(s==1) lim=d[p];
	for(int i=0;i<=lim;i++)
	{
		int del=0;
		if(i==0&&dig==0&&ld) del=0;
		else if(i==dig) del=1;
		res+=dfs(dig,p-1,ld&(i==0),s&(i==d[p]),sum+del);
	}
	return f[p][sum][ld][s]=res;
}
LL solve(LL x,int dig)
{
	n=0;
	while(x)
	{
		d[++n]=x%10;
		x/=10;
	}
	memset(f,-1,sizeof(f));
	return dfs(dig,n,1,1,0);
}
int main()
{
	LL a=rd(),b=rd();
	for(int i=0;i<=9;i++)
		printf("%lld ",solve(b,i)-solve(a-1,i));
	return 0;
}