数位dp的题目,也可以理解为记忆化搜索,题意很简单,关键是对题目求解的方式。
先对A求其F,然后将B按位分解,从高位向低位递归求解,直至当前数字情况已经计算过或者已经到第0位,第0位的话如果满足条件,则前面递归走过的序列肯定是一个满足题意的解,返回1,然后回溯的时候记录dp[i][j],表示i到0位数小于j的数有多少个,如果前面计算的时候计算的是B的上边界,这是显然不能按dp[i][j]计算,因为i位数中肯定不能取完,要满足小于B才行,如果不是上边界,则可以使用或计算dp[i][j]。这样通过记忆化搜索,便能得到答案。
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int dp[11][5001];
int num[11];
int p[11];
int cnt;
int m;
int solve(int l,int s,bool k) //l表示进行到第几位,s表示前面几位的F为多少,k表示前面是否是边界
{
//cout<<l<<" "<<s<<" "<<k<<endl;
if (s>m) //如果前面几位的F已经大于F(A),则该情况不符合题意,该路径满足题意的数字数为0
return 0;
if (l<=0) //如果满足上面一条条件,且当前是第0位,说面前面形成的路径刚好是一个满足题意的数字,返回1
return 1;
if (!k&&dp[l][m-s]!=-1) //如果当前计算的情况不是边界情况,且dp[i][j]之前已经计算过,则可以直接利用
return dp[l][m-s];
int n=9; //初始化边界为9
if (k) //如果之前计算的是边界,如B=987,前面路径为98的话,当前这位最多只能取7才小于等于B,否则能取9
n=num[l];
int ss=0;
for (int i=0;i<=n;i++)
ss+=solve(l-1,s+i*p[l],k&&(i==n)); //记录所有可能解的和,当前情况是边界情况当且仅当之前的情况是边界情况且当前取到边界
if (!k) //如果不是边界情况,记录dp[i][j]
dp[l][m-s]=ss;
return ss;
}
void gets(int x) //获得A的F
{
int l=1;
m=0;
while (x)
{
int b=x%10;
x=x/10;
m=m+b*p[l];
l++;
}
}
void getnum(int x) //将B按位分解,num[0]记录有几位
{
int l=0;
while (x>0)
{
l++;
num[l]=x%10;
x=x/10;
}
num[0]=l;
}
int main()
{
p[1]=1;
for (int i=2;i<=10;i++)
p[i]=p[i-1]*2;
int r;
scanf("%d",&r);
memset(dp,-1,sizeof(dp));
for (int i=1;i<=r;i++)
{
int a,b;
//cin>>a>>b;
scanf("%d%d",&a,&b);
gets(a);
getnum(b);
cnt=solve(num[0],0,true); //开始时,已经取到边界情况
printf("Case #%d: %d\n",i,cnt);
}
}