数位dp是与数位有关的区间统计 参考: 点击打开链接
思想是将数字拆分,dp[i][j] 长度为i的数字有多少个满足j条件的,从000~999
统计时,计当前数字为x,则其后面数字的变化的倍数是0~x-1 后面接000~999这样刚好统计不到这个数字n本身。
自底向上要注意这种方法没有考虑n这个数字本身是否满足要求,所以统计区间 [m,n] 的计算是f(n+1)-f(m)
dfs 状态设计
对必须要有XX数字则用pre保证,否则还是要有tag标记.
那么什么样的前缀对后面子数有相同的结果,余数的妙用对于mod类.state设计用状压
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]
一般有两种写法,自底向上和递归+记忆化搜索
/* ***********************************************
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));
}///eg : 对于50,0~5 当0的时候判断出来为0则全为0 若没有pre这个存储则是通过其他变量控制但是易出错
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;
}