这个题意思是每一个数都有一个对应的权值,比如一个数字a有n位数,那么他的权值为F(a) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1 ,这里的Ai表示数字a的第i位数,那么给两个数字a b,求解在区间[0,b]中有多少数字满足:F(x)>= F(a)
看到这个题,我们也可以想到这个权值的大小和数字的大小没有任何关系,只是和数位上的数字有关系,并且求解的是区间内成立个数的问题,那么就可以想到是个区间DP问题
但是我们要是记录状态的话:dp[i][j]:当前位置为i,并且现在前i位的权值已经为j的情况下,后面满足条件的个数,如果这样定义状态的话,那么每次输入都要去更新dp数组了,因为每次给出的F(a)都是不同的,也就是当F(a)不同的情况下,dp[2][50]的数值只是对当前计算的F(a)是有效的
如果向上面所说的,那我们定义dp[i][j][val]:当前位置为i、F(a) = val 、前i为的权值已经为j的情况下,后面满足条件的个数,这样的话实质上和我们上面的情况没有什么区别,只不过是牺牲了空间,时间确实得到了提升,并且在这个题中最大的val大约为2600,这样的话空间还可以,但是在其他的题目中val可能就不止这些,所以这个还有可能会内存超限
那么我们看一下解决的办法:既没有牺牲空间、时间也会得到提升
我们知道不管F(a)是什么数值,那么比如在已经知道前i位的权值为x情况下,那么后面几位最大的能够到达的权值已经确定了,也就是F(a) - x,那么当再次到达这个位置时,后面权值最多再次到达 F(a) - x的话,我们就可以用前面的那个状态了,所以我们就可以用 dp[i][j]:当前位置为i、后面最多能够使用的权值为j的情况下,满足条件的个数
那么这样我们确定了状态的表示,那么剩下的大概就是数位DP的模板题了
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <iomanip> #include <cmath> #include <vector> #include <set> #include <queue> #include <map> #include <math.h> #include <cmath> #define INF 0x3f3f3f3f using namespace std; const int MAXN = 5000; int dp[10][MAXN]; int a[10]; int F(int x) { if(x == 0) return 0; int ans = F(x/10); return ans * 2 + x % 10; } int all; int dfs(int pos,int sum,bool limit) { if(pos < 0) return sum <= all; if(sum > all) return 0; if(!limit && dp[pos][all - sum] != -1) return dp[pos][all-sum]; int up = limit?a[pos]:9; int ans = 0; for(int i = 0;i <= up;i ++) ans += dfs(pos-1,sum + i * (1<<pos), limit && i == a[pos]); if(!limit) dp[pos][all - sum] = ans; return ans; } int Solve(int x) { int pos = 0; while(x) { a[pos ++] = x % 10; x /= 10; } return dfs(pos-1 , 0, true); } int main() { int T,a,r; scanf("%d",&T); int cas = 1; memset(dp,-1,sizeof(dp)); while(T--) { scanf("%d%d",&a,&r); all = F(a); printf("Case #%d: %d\n",cas++,Solve(r)); } }