数位DP入门
直接上题:
专题II:
HDU-2089 不要62
参考博客例1即为这道题,这里主要对代码进行较详细的分析:
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
int a[20];
int dp[20][2];
int dfs(int pos,int pre,int sta,bool limit)
{
if(pos==-1) return 1;
if(!limit && dp[pos][sta]!=-1) return dp[pos][sta];
int up=limit ? a[pos] : 9;
int tmp=0;
for(int i=0;i<=up;i++)
{
if(pre==6 && i==2)continue;
if(i==4) continue;//都是保证枚举合法性
tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);
}
if(!limit) dp[pos][sta]=tmp;
return tmp;
}
int solve(int x)
{
int pos=0;
while(x)
{
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
int le,ri;
//memset(dp,-1,sizeof dp);可优化
while(~scanf("%d%d",&le,&ri) && le+ri)
{
memset(dp,-1,sizeof dp);
printf("%d\n",solve(ri)-solve(le-1));
}
return 0;
}
这里详细理解变量sta的作用,此题的dp是记录一个位置后面的数位可能的所有情况数,我们可以先假设不用sta变量和limit变量:举个例子:当x=1000时,dfs首先搜百位是0 十位是0的情况,个位此时有0~ 5、7~ 9共9种可能,由于当pos = -1时dfs返回1,所以记录下来得到dp[0]=9,又因为十位数也有0~3、 5、 7~ 9共9种可能,记录下来得到dp [ 1 ]=81 ,很明显这个dp没有考虑题目所要求的不能出现62。
sta就起了将前一位数是6和前一个数不是6这两种情况分开记录的作用:加入sta后,百位是0,十位是0时,个位有9种可能,记录是用dp[0][1]=9,当十位是6时,个位的记录是用dp[0][0]=8。
所以到计算百位(此时百位为0)时,十位为0 ~ 3、5、7 ~ 9使用的是dp[0][1], tmp得到72,十位为6使用的是dp[0][0]=8得到tmp+8=80。所以得到dp[2][0]=80。
计算到百位为6时,十位有0 ~1、 3、5 ~ 9共8种选法(其中十位为6时dp[1][1]=8),得dp[2][1]=71;以此递推。
另一AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
int a[20];
int dp[20];
int dfs(int pos,int pre,int sta,bool limit)
{
if(pos==-1) return 1;
if(!limit && dp[pos]!=-1&& sta!=1 ) return dp[pos];
if(!limit && dp[pos]!=-1&& sta==1 &&pos==0) return dp[pos]-1;
if(!limit && dp[pos]!=-1&& sta==1 &&pos>=1) return dp[pos]-dp[pos-1];
int up=limit ? a[pos] : 9;
int tmp=0;
for(int i=0;i<=up;i++)
{
if(pre==6 && i==2)continue;
if(i==4) continue;//都是保证枚举合法性
tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);
}
if(!limit) dp[pos]=tmp;
return tmp;
}
int solve(int x)
{
int pos=0;
while(x)
{
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
int le,ri;
//memset(dp,-1,sizeof dp);可优化
while(~scanf("%d%d",&le,&ri) && le+ri)
{
memset(dp,-1,sizeof dp);
printf("%d\n",solve(ri)-solve(le-1));
}
return 0;
}
又因为数据较小可以暴力a题:
#include<stdio.h>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<stack>
#define mod 1000000007
#define esp 1e-9
using namespace std;
int dp[1000005];
int main()
{
int cnt=0;
for(int i=0;i<=1000000;i++)
{
dp[i]=cnt;
int cmp=i;
while(cmp)
{
if(cmp%10==4){
cnt++;
dp[i]=cnt;
break;
}
if(cmp%10==2&& (cmp/10)%10==6)
{
cnt++;
dp[i]=cnt;
break;
}
cmp/=10;
}
}
int n,m;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
int res=(m-n+1)-(dp[m]-dp[n-1]);
printf("%d\n",res);
}
return 0;
}