數位DP/數位統計 初探

一點廢話:

本沙茶從昨天上午下午到現在一直看這鬼東西。。噁心死了啊有木有!!

以前做過一次windy數,代碼調了一億年...

經過兩天的專題訓練,數位dp一點從不會到入了一點門了吧。

以前是什麼都不會,不會想也不會寫。現在至少稍微能寫一些大水題了。。

(其實還是什麼都不會)

好了不說廢話了。 


先說寫法:

最開始我是寫的預處理再循環,前幾道題那樣寫挺不錯,個人認爲挺好的,後面也會放出這種代碼。
做到後面的題問題就出來了,由於太沙茶而導致智商不夠.. 然後看了很久記憶化DFS版,然後後面幾道就寫DFS version了。
主要是DFS比較無腦。。(其實差不多啦,,真的嗎。。)

入門題目:

個人認爲該順序是由易至難的,都是網上或一些論文的一些經典入門題吧。
一些題網上題解很多我就不寫了。(當然也只有我這種蒟蒻纔看題解)

(一)Bomb

題目簡述:求[a,b]之間含有"49"的數的個數。

預處理循環版:
#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
typedef long long LL;
LL f[20][3];
void Prep(){
	f[0][0]=1;
	rep(i,1,19){
		f[i][0]=9*f[i-1][0]+8*f[i-1][1];
		f[i][1]=f[i-1][0]+f[i-1][1];
		f[i][2]=f[i-1][1]+10*f[i-1][2];
		}
}
LL count(LL x){
	int len=0,a[22];LL res=0;
	bool exist=0;
	while (x) a[++len]=x%10,x/=10;a[len+1]=-1;
	per(i,len,1){
		res+=f[i-1][2]*a[i];//-1+1
		if (exist) res+=(f[i-1][0]+f[i-1][1])*a[i];
		if (!exist&&a[i]>4) res+=f[i-1][1];
		if (a[i+1]==4&&a[i]==9) exist=true;
		}
	if (exist) res++;
	return res;
}
LL n;int T;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	Prep();
	scanf("%d",&T);
	while (T--){
		scanf("%I64d",&n);
		printf("%I64d\n",count(n));
		}
}


Dfs version:
#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
typedef long long LL;
LL f[25][4];int a[25];
LL dfs(int pos,int u,bool lim){
	if (pos<=0) return u==2;
	if (!lim&&f[pos][u]!=-1) return f[pos][u];
	LL res=0;
	int top=lim?a[pos]:9;
	rep(i,0,top){
		int v=u;
		if (u==1&&i==9) v=2;
		if (u==1&&i!=9&&i!=4) v=0;
		if (u==0&&i==4) v=1;
		res+=dfs(pos-1,v,lim&&i==top);
		}
	//printf("%d %d %d %d\n",pos,u,lim,res);
	return lim?res:(f[pos][u]=res);
}
LL count(LL x){
	int pos=0;
	while (x) a[++pos]=x%10,x/=10;a[pos+1]=-1;
	return dfs(pos,0,1);
}
int main(){
	freopen("a.in","r",stdin);
	freopen("c.out","w",stdout);
	memset(f,-1,sizeof f);
	int T;LL n;
	scanf("%d",&T);
	rep(i,1,T){
		scanf("%I64d",&n);
		printf("%I64d\n",count(n));
		}
}

(二)不要62

題目簡述:[a,b]區間內沒有“62”且沒有“4”的數的個數。
和題一基本相同。。

#include <cstdio>
#include <algorithm>
#define per(i,r,l) for (int i=r;i>=l;--i)
#define rep(i,l,r) for (int i=l;i<=r;++i)
int f[10][10];
int pow[10];
void prep(){
	rep(i,0,9) f[1][i]=(i==4?0:1);
	pow[1]=1;
	rep(i,2,9) rep(j,0,9){
		pow[i]=pow[i-1]*10;
		rep(k,0,9)
			if (j!=4&&!(j==6&&k==2)) f[i][j]+=f[i-1][k];
		}
}
int count(int x){
	int res=0,len=1,u=-1,v=-1;
	while (x>=10*pow[len]) len++;
	per(i,len,1){
		int u=x/pow[i];
		x-=u*pow[i];
		rep(j,0,u-1) if (!(v==6&&j==2))res+=f[i][j];
		if ((v==6&&u==2)||u==4) break;
		if (i==1) res++;
		v=u;
		}
	return res;
}
int n,m;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	prep();
	while (scanf("%d%d",&n,&m)!=EOF){
		if (n+m==0) return 0;
		printf("%d\n",count(m)-count(n-1));
		}
}

(三)windy數

題目描述:不含前導零且相鄰兩個數字之差至少爲2的正整數被稱爲windy數,求[a,b]之間有多少windy數。
這代碼是五十年前寫的,可以對比一下發現以前寫的是多麼醜。。(雖然現在仍然很醜~)
/*
	f[i][j]->第i位爲j之內的個數
	f[i][j]=Sum{f[i-1][k]} abs(j-k)>=2
*/
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL Base[12];
LL f[12][10];//f[i][j]->位數i 最高位爲j 的個數
void preW()
{
	Base[1]=1;
	for (int i=2;i<=11;++i)Base[i]=10*Base[i-1];
	for (int i=0;i<=9;++i) f[1][i]=1; 
	for (int i=2;i<=11;++i)
	 for (int j=0;j<=9;++j)
	  for (int k=0;k<=9;++k)
		if (abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
LL Count(LL n)
{
	if (n==0) return 0;
	LL ret=0;
	int p=1;
	while (Base[p+1]<=n){
		for (int i=1;i<=9;++i)
			ret+=f[p][i];
		p++;
		}
	int pre=n/Base[p];
	for (int i=1;i<=pre-1;++i)
		ret+=f[p][i];
	if (p==1) return ret+1;//本身也是
	n-=pre*Base[p];
	int now=n/Base[p-1];
	for (int i=p-1;i>=1;--i){
		for (int j=0;j<now;++j)
			if (abs(j-pre)>=2)
				ret+=f[i][j];
		if (abs(pre-now)<2) break;//後面的都不能
		if (i!=1){
			pre=now;
			n-=pre*Base[i];
			now=n/Base[i-1];
			}
		else
			ret++; //i==1時abs(pre-now)<2 -->自己也是
		}
	return ret;
}
int main(){
	preW();
	LL n,m;
	scanf("%lld%lld",&n,&m);
	printf("%lld\n",Count(m)-Count(n-1));
}

(四)B-number

題目描述:求[a,b]之間被13整除且含有“13”的數的個數。

給跪了,這題調了一晚上,還是沒過,第二天早上一眼發現晚上交的全都沒刪freopen!!!!!
#include <cstdio>
#include <algorithm>
#define per(i,r,l) for (int i=r;i>=l;--i)
#define rep(i,l,r) for (int i=l;i<=r;++i)
typedef long long LL;
int f[12][10][14][2];
LL pow[12];
void Preprocess(){
	f[0][0][0][0]=1;pow[1]=1;
	rep(i,2,11) pow[i]=pow[i-1]*10;
	rep(i,1,10) rep(j,0,9){
		rep(k,0,9) rep(mod,0,12){
			if (j==1&&k==3){
				f[i][j][((LL)j*pow[i]+mod)%13][1]+=f[i-1][k][mod][0]+f[i-1][k][mod][1];
			}else{
				f[i][j][((LL)j*pow[i]+mod)%13][0]+=f[i-1][k][mod][0];
				f[i][j][((LL)j*pow[i]+mod)%13][1]+=f[i-1][k][mod][1];
				}
			}
		}
}
int count(int x){
	int res=0,mod=0,len=1,v=-1,u=-1;bool exist13=0;
	while(x>=pow[len+1]) len++;
	per(i,len,1){
		u=x/pow[i];x-=u*pow[i];
		rep(j,0,u-1){
			res+=f[i][j][(13-mod)%13][1];
			if (exist13||(v==1&&j==3)){
				res+=f[i][j][(13-mod)%13][0];
				}
			}
		if (v==1&&u==3)exist13=true;//!!
		mod=(u*pow[i]+mod)%13;
		if (i==1&&exist13&&mod==0) res++;
		v=u;
		}
	return res;
}
int n;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	Preprocess();
	while (scanf("%d",&n)!=EOF)
		printf("%d\n",count(n));
}

(五)Amount of Degrees

題目描述:求給定區間[X,Y]中滿足下列條件的整數個數:這個數恰好等於K個互不相等的B的整數次冪之和。

#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
int f[33][33];
void Prep(){
	f[0][0]=1;
	rep(i,1,31){
		f[i][0]=1;
		rep(j,1,i) f[i][j]=f[i-1][j]+f[i-1][j-1];
		}
}
int count(int x,int k,int b){
	int len=0,res=0,a[33];
	while (x) a[++len]=x%b,x/=b;
	per(i,len,1){
		if (a[i]==1) res+=f[i-1][k--];
		if (a[i]>1){res+=f[i-1][k]+f[i-1][k-1];break;}
		if (k==0) res++;
		if (k<=0) break;
		}
	return res;
}
int x,y,k,b;
int main(){
	Prep();
	while(scanf("%d%d%d%d",&x,&y,&k,&b)!=EOF)
		printf("%d\n",count(y,k,b)-count(x-1,k,b));
}


(六)self 同類分佈

題目簡述:給出a,b,求出[a,b]中各位數字之和能整除原數的數的個數。

這道題我是枚舉數字的和,每一個和分別重新處理一次,要不然不知道他模多少。還有要MLE。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
typedef long long LL;
LL f[20][173][173];int MOD;
LL pow[20];
LL dfs(int pos,int sum,int mod,bool lim,int a[]){
	if (pos*9<sum) return 0;
	if (pos<=0) return sum==0&&mod==0;
	if (!lim&&~f[pos][sum][mod]) return f[pos][sum][mod];
	int top=lim?a[pos]:9;LL res=0;
	rep(i,0,top){
		int s=sum-i,m=(int)(((LL)mod+(LL)i*pow[pos])%(LL)MOD);
		if (s<0) break;
		res+=dfs(pos-1,s,m,lim&&i==top,a);
		}
	return lim?res:(f[pos][sum][mod]=res);
}
int a[20],b[20]; 
LL count(LL x,LL y){
	int lena=0,lenb=0;LL res=0;
	while (y) b[++lenb]=y%10,y/=10;
	while (x) a[++lena]=x%10,x/=10;
	for (int i=1;i<=lenb*9;++i){
		memset(f,-1,sizeof f);MOD=i;
		res+=dfs(lenb,i,0,1,b);
		res-=dfs(lena,i,0,1,a);
		}
	return res;
}
LL x,y;
int main(){
	pow[1]=1;
	rep(i,2,19) pow[i]=pow[i-1]*10;
	scanf("%lld%lld",&x,&y);
	printf("%lld\n",count(x-1,y));
}

小結:

是不是大水題。。
數位DP有兩大問題吧,
1.如何找狀態。
2.如何寫。
第二個問題其實不是什麼問題,多寫幾個代碼就會了。
第一個問題就比較難了。(像我這種智商真心拙計的蒟蒻啊,只能跪跪跪跪跪了~)
找狀態要抓住問題的特徵分析吧,相當於對數字完整分類,記錄關鍵信息,同時這個又要能夠遞推才行,不然不就相當於深搜麼。。
有些題可能要注意前導零什麼的。。


循環和dfs記錄的狀態是有區別的:
循環f[i][j]記錄的是從低位到第i位狀態是balabala時的值。
記憶化dfs記錄的是從高位到第i位的狀態是balalba時的值。

好像真的兩種其實真的沒有太大的區別的。
發佈了41 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章