1. 程式人生 > >動態規劃(DP)不要62

動態規劃(DP)不要62

題意:問在n,m之間不含4和62序列(4和62為不吉利數)的數字有多少個。
題解:把狀態分為3種,

dp[i][0],表示不存在4和62,最高位是不是2沒有要求.
dp[i][1],表示不存在4和62,且最高位為2(dp[i][1]是dp[i][0]的一個子集)

dp[i][2],表示存在4或62

預處理的過程十分類似進位制轉換的求程,例如,求(a3a2a1a0)10轉化為二進位制的過程,我們會預處理:

bit0=2^0

bit1=2^1

bit2=2^2

bit3=2^3

然後result = a3*bit3+a2*bit2+a1*bit1+a0*bit0.

狀態轉移方程


dp[i][0]=dp[i-1][0]*9-dp[i-1][1];                            //在高位補除4外的9個數,因為補了6,需要將下一位為2的情況去掉
dp[i][1]=dp[i-1][0];                                               //在高位補2
dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0];         //三種情況,低位已經有倒黴數,則高位任取10種可能值,低位為2,

    高位取6,低位沒有倒黴數,高位取4

如果將dp[i][j]看作進位制轉換的基底,將s[i]看作當前位的數值,則下面的程式碼看起來十分類似進位制轉換.具體從程式碼中體會.

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int dp[10][3];
int DP(int x)
{
	bool isUnlucky=false; //初始設為false,初始狀態是幸運的
	int s[15];
	int Index=0,summ=x,unLuckyNum=0;
	//為了適合人類習慣,反轉數字,且從1開始數起
	for(;x;x/=10)
		s[++Index] = x % 10 ;
	s[Index+1]=0;

	for(int i=Index;i>0;--i) //處理順序從高到低
	{
		//如果本位是s[i],而且前一種狀態存在倒黴數,則倒黴數會新增s[i]*dp[i][2] (1)
		//例如s[i][****4****],s[i][***62*****],s[i][***4****62***]這種情況,對應dp[i][2],
		
		unLuckyNum+=(dp[i-1][2]*s[i]);

		//分兩個情況處理
		if(isUnlucky) 
			//case1:出現了倒黴數,4,62,這是上一輪產生的倒黴數,例如62[****],
			//(i指向2的下一位),或者4[******],(i指向4的下一位),與(1)區分開來
			//如果isUnlucky,實際上執行了兩步,(1)處理歷史遺留問題s[i][****4****],s[i][***62*****],
			//s[i][***4****62***],這裡的[]屬於dp[i][2],
			//(2)處理新產生的問題,例如:62[***]或4[****],這裡的[]屬於dp[i][0](低位可取任意值)
			//本位已經是倒黴數,低位是任意數它都會是倒黴數,所以,統計後面的所有情況即可
			//(不需再處理dp[i][2],上面已經處理過了),難點!類比進位制轉換就明白了
			unLuckyNum+=(dp[i-1][0]*s[i]);
		else         
		{	//case2:不存在4或62(即只能在dp[i][0]和dp[i][1]中取)
			//>4的意思是必定途經4(是否途經6下一步再判斷),例如5,則累計的時候需要
			//統計1,2,3,4,5.5種情況最高位取4,則後面任取,實際上,一個數"僅僅"大於4(類似5),
			//只執行了一步操作:unLuckyNum+=dp[i-1][0];
			if(s[i]>4)            
				unLuckyNum+=dp[i-1][0];
			//出現4和6,統計前一狀態最高位是2的情況,則倒黴數出現的情況是本位為6,前一狀態最高位是2
			//實際上,一個數如果>6,則它也>4,所以>6執行了這兩步操作:unLuckyNum+=dp[i-1][0],(高位是4時)
			//unLuckyNum+=dp[i-1][1];(高位是6位時)
			if(s[i]>6)
				unLuckyNum+=dp[i-1][1]; //處理本位是6的情況,本位是4的情況上面已經處理過了
			if(s[i+1]==6&&s[i]>2)
				unLuckyNum+=dp[i][1];//如果高位出現6,則取本位出現2的情況
		}
		if(s[i]==4||s[i+1]==6&&s[i]==2) //判斷本位是否unlucky
			isUnlucky=true;
	}
	return summ-unLuckyNum;//所有的數減去倒黴數
}
int main()
{
	int a,b;
	memset(dp,0,sizeof(dp));
	//預處理,算出所有可能
	dp[0][0]=1; 
	//dp[i][0],表示不存在4和62,最高位是不是2沒有要求.
	//dp[i][1],表示不存在4和62,且最高位為2(dp[i][1]是dp[i][0]的一個子集
	//dp[i][2],表示存在4或62
	for(int i=1;i<=8;++i)
	{
		//高位分別補除了4的9個數字,因為補了6,所以需要減去前一種最高位是2的情況
		dp[i][0]=dp[i-1][0]*9-dp[i-1][1];
		//在不含倒黴數的高位補2
		dp[i][1]=dp[i-1][0];			
		//各種出現4和62的情況,前一種情況已經出現了倒黴數,則高位有10種取法,
		//前邊最高位是2,則高位取6,
		//前邊沒有倒黴數且最高位不是2,則高位取4
		dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0];
	}
	/*
	for(int i=0;i<10;++i)
	{
		for(int j=0;j<3;++j)
			cout<<dp[i][j]<<' ';
		cout<<endl;
	}
	*/
	for(;scanf("%d%d",&a,&b);)
	{
		if(a+b==0) break;
		printf("%d\n",DP(b+1)-DP(a));
	}
	return 0;
}
上面的方法思維複雜,用時少,下面還有一種方法容易理解,但是,用時稍多,125ms。只需要蠻力打表。