>传送门<
题意:对于一个有n位(这n位从高位到低位分别是An,An-1,An-2 ... A2,A1)的十进制数,我们定义它的权值F(x)=An*2n-1 + An-1*2n-2 + ... + A2*2 + A1*1.现在给你两个数A,B,请计算[0,B]范围内有多少个权值<=F(A)的数
思路:(这个下面文字是有点多,可能是我太弱了,在刚开始学的时候只有这样我才能理解QAQ~)
其实F(x)只是给每一个数位带上一个权值v = 2^(p-1),F(x)最大是值不会超过5000,我们完全可以抛开权值来思考,写代码的时候再加上权值即可,这样思考和写草稿之类的会方便很多,不考虑每一位的权值的话即是数位的前缀和。
很容易想到一个dp式是dp[pos][sum];表示当前在第pos位,前缀和为sum的答案,快速把数位dp敲完,交上去,然后会发现TLE掉了,这题的时限只有500ms,TLE的原因是什么呢,就是记忆化不够彻底。
在做数位dp入门题 不要62 的时候可能有些人会注意到,dp数组只需要初始化一次即可,这是因为不要62题意中是不包含4和连续的62的数的个数,这里的条件是一个数本身的性质,也就是说,一个数有没有4或者连续的62和你输入的[l,r]区间是无关的,比如1234是不合法的,无论你输入1 - 1000 还是 1 - 10000,1234都是不合法的。
但是这里是不一样的,题意要求小于F(a),而a是输入的,而dp数组定义是:dp[pos][sum],表示当前在第pos位,前缀和为sum的答案, 如假设上限是55555, 那么123xxx 和 321xxx 的答案都是 dp[3][6] ,这就是记忆化的结果,因为我不需要考虑前面具体的数的123 还是321 114之类的,只要他们的前缀和sum相同并且在同样的数位,后面xxx的情况都是一样的,因为后面的约束条件都是和不大于F(a)-sum。
问题就是在这个地方,由于我的记忆化是和输入的a也就是F(a)有关,所以我不能把它当作一个数的性质来记忆话,比如同样是123xxx,对于F(a) = 10和F(a) = 20,后面xxx可行的情况是不一样的,a=10的时候,后面的约束是不大于F(a)-sum = 4,a = 20的时候约束为不大于F(a)-sum = 14,从而导致了每次输入不同的a都要重新初始化dp数组,重新搜索一次。这里可以通过对dp状态的定义做一点小小的改变,使得约束条件变为与F(a)无关,从而不需要每次初始化重新搜索,定义dp[pos][sum],表示当前在第pos位,F(a)-sum的答案(sum为到第pos位为止的数位前缀和),这时在第pos位的约束条件是后面所有的数的和不大于sum,这时记忆化的是sum,是与F(a)无关的,比如当F(a) = 10和F(a) = 20时,F(a) = 10时,123xxx、321xxx等对应的是dp[3][4], F(a)=20时,556xxx、466xxx对应的也是dp[3][4],因为2种情况的pos和F(a)-sum都是相同的,所以记忆化一次即可,不必初始化
总结一下,在设计数位dp状态时最好把每次询问的变量作为初始值而不是状态转移、判定时依赖的量(比如第一个状态判定条件是F(x)<=F(a),就依赖于当前F(a),这样就不好),这样才可以最大限度的发挥记忆化的作用。
Code
#include<cstdio> #include<cstring> using namespace std; const int maxn = 1e4+5; int A, B, t, tot; int a[12], dp[12][maxn]; int f(int x) { int ans = 0, cnt = 0; while(x){ ans += (x%10)*(1<<(cnt++)); x /= 10; } return ans; } int dfs(int pos, int sum, int limit) { if(pos==0) return sum<=tot; if(sum>tot) return 0; if(!limit&&dp[pos][tot-sum]!=-1) return dp[pos][tot-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-1)), limit&&i==a[pos]); if(!limit) dp[pos][tot-sum] = ans; return ans; } int solve(int x) { int pos = 1; while(x) { a[pos++] = x%10; x /= 10; } return dfs(pos-1, 0, 1); } int main() { int kase = 1; memset(dp,-1,sizeof dp); scanf("%d", &t); while(t--) { scanf("%d%d", &A, &B); tot = f(A); printf("Case #%d: %d ", kase++, solve(B)); } return 0; }
参考文章:
https://www.cnblogs.com/AbandonZHANG/p/4114122.html
http://www.bubuko.com/infodetail-2320548.html