首页 > 代码库 > HDU 3555 Bomb ,HDU 2089 深刻学习数位dp (各种方法乱用)

HDU 3555 Bomb ,HDU 2089 深刻学习数位dp (各种方法乱用)

数位dp是与数位有关的区间统计 参考: 点击打开链接

思想是将数字拆分,dp[i][j] 长度为i的数字有多少个满足j条件的,从000~999
统计时,计当前数字为x,则其后面数字的变化的倍数是0~x-1 后面接000~999这样刚好统计不到这个数字n本身。

eg:

对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。 
例如,在十进制下,计算[10000,54321]内的数字和,我们可以将其分解为: 
[10000,19999],[20000,29999],[30000,39999],[40000,49999],[50000,54321]。 
前四个区间如果可以直接解决,则只需处理最后一个区间,进一步将最后一个区间划分为:
[50000,50999],[51000,51999],[52000,52999],[53000,53999],[54000,54321]。
同理将最后一个区间划分下去,最后可以得到以下区间划分: 
[10000,19999],[20000,29999],[30000,39999],[40000,49999], 
[50000,50999],[51000,51999],[52000,52999],[53000,53999], 
[54000,54099],[54100,54199],[54200,54299], 
[54300,54309],[54310,54319], 
[54320,54321]

一般有两种写法,自底向上和递归+记忆化搜索

自底向上要注意这种方法没有考虑n这个数字本身是否满足要求,所以统计区间 [m,n]  的计算是f(n+1)-f(m)


/* ***********************************************
Author        :bingone
Created Time  :2014-12-25 13:44:50
File Name     :HDU3555.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define inf 0x3f3f3f3f
#define maxn (10+1000)
typedef long long ll;
using namespace std;
int M,T;
ll N,dp[22][3];
int num[22];
ll solve()
{
	ll res=0,cache=N;
	int tag=0,len=1;
	while(cache)
		num[len++]=cache%10,cache/=10;
	for(num[len--]=0;len;len--){
		res+=num[len]*dp[len-1][2];
		if(tag==1) {res+=num[len]*dp[len-1][0];continue;}
		if(num[len]>4) res+=dp[len-1][1];
		if(num[len]==9 && num[len+1]==4) tag=1;
	}
	return res;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
	dp[0][0]=1;
	for(int i=1;i<22;i++){
		dp[i][0]=dp[i-1][0]*10-dp[i-1][1];
		dp[i][1]=dp[i-1][0];
		dp[i][2]=dp[i-1][2]*10+dp[i-1][1];
	}
	scanf("%d",&T);
	while(T--){
		scanf("%I64d",&N);N++;
		printf("%I64d\n",solve());
	}
    return 0;
}

其实很多数位dp都是用dfs模板写

typedef long long LL;
const int maxn=22;
int dig[maxn];
LL f[maxn]/* [TODO] */;

LL dfs(int pos,/* TODO */,int limit){
    if (pos<0) return /* TODO */;
    if (!limit&&f[pos]/* [TODO] */!=-1) return f[pos]/* [TODO] */;
    LL res=0;
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        res+=dfs(pos-1,/* TODO */,limit&&(i==last));
    }
    if (!limit) f[pos]/* [TODO] */=res;
    return res;
}

LL solve(LL n){
    int len=0;
    while (n){
        dig[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,/* TODO */,1);
}

于是参考别人的代码这个题有两种dfs

1.按照模板写的,pos表示当前处理到的位置,istrue记录数字处理位置之前的部分是否满足已经满足题意。

limit 该位是否有限制,比如n=1234,现在转移到12,如果下一位选3,那么再下一位就有上限, 上限为4,如果不选3,那么下一位就没限制,最高位9,转移能保证转移到数比n小

这样一来dfs的方法就可以处理n这个数字本身了,因为处理到pos==-1时候istrue已经表明状态。

LL dp[N][10][2];
LL dfs(int pos,int pre,int istrue,int limit) {
    if (pos < 0) return istrue;
    if (!limit && dp[pos][pre][istrue] != -1)
        return dp[pos][pre][istrue];
    int last = limit ? dig[pos] : 9;
    LL ret = 0;
    for (int i = 0; i <= last; i++) {
        ret += dfs(pos-1,i,istrue || (pre == 4 && i == 9),limit && (i == last));
    }
    if (!limit) {
        dp[pos][pre][istrue] = ret;
    }
    return ret;
}


2.

dp[25][3]和递推方法的状态表达一样。

这个方式就很简单直观了

__int64 Dfs (int pos,int s,bool limit)  //s为之前数字的状态  
{  
    if (pos==-1)  
        return s==2;  
    if (limit==false && ~dp[pos][s])  
        return dp[pos][s];  
    int i ,end=limit?bit[pos]:9;  
    __int64 ans=0;  
    for (i=0;i<=end;i++)  
    {  
        int nows=s;  
        if(s==0 && i==4)  
            nows=1;  
        if(s==1 && i!=9)   //前一位为4  
            nows=0;  
        if(s==1 && i==4)  
            nows=1;  
        if(s==1 && i==9)  //49  
            nows=2;  
        ans+=Dfs(pos-1 , nows , limit && i==end);  
    }  
    //limit==true 则说明有限制,即所有可能并没有被全部记录
    //limit==false则说明之后的分支状态已经搜索完全  
    return limit?ans:dp[pos][s]=ans;  
}

HDU 2089 

/* ***********************************************
Author        :bingone
Created Time  :2014-12-24 9:57:30
File Name     :HDU2089.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define inf 0x3f3f3f3f
#define maxn (10+1000)
using namespace std;
int N,M,T;
int num[10][3];
int slove(int n)
{
	int res=0;
	int we[10],tmp,wecnt;
	for(wecnt=1,tmp=n;tmp;wecnt++,tmp/=10)
		we[wecnt]=tmp%10;
	bool tag=false;
	for(we[wecnt--]=0;wecnt;wecnt--){
		res+=we[wecnt]*num[wecnt-1][2];
		if(tag==true){res+=we[wecnt]*num[wecnt-1][0];continue;}
		if(we[wecnt]>4)
			res+=num[wecnt-1][0];
		if(we[wecnt]>6)
			res+=num[wecnt-1][1];
		if(we[wecnt+1]==6 && we[wecnt]>2)
            res+=num[wecnt-1][0];
		if(we[wecnt]==4 || (we[wecnt]==2 && we[wecnt+1]==6) )
			tag=true;
	}
	return n-res;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
	num[0][0]=1;
	for(int i=1;i<7;i++)	/// jili jili and 2 notjili
	{
		num[i][0]=num[i-1][0]*9-num[i-1][1];
		num[i][1]=num[i-1][0];
		num[i][2]=num[i-1][2]*10+num[i-1][1]+num[i-1][0];
	}
	while(~scanf("%d%d",&N,&M)){
		if(N==0 && M==0) break;
		printf("%d\n",slove(M+1)-slove(N));
	}
    return 0;
}


HDU 3555 Bomb ,HDU 2089 深刻学习数位dp (各种方法乱用)