题意:给定一个数,让你求从1到这个数的所有数字中含49的数字数量。
析:看到这一个题,首先想到的就是暴力,可是,我一看数的大小就放弃了,2^63-1。。。
还是问学长吧,学长说数位DP,听他说了好久,才明白,好复杂啊,在这里分享一下。
这是我参考的博客网址:http://www.cnblogs.com/liuxueyang/archive/2013/04/14/3020032.html
d[i][0]:表示前 i 位中不含49的数量;
d[i][1]:表示前 i 位中不含49,但是最高位是9(也就是说第 i 位是9),的数量;
d[i][2]:表示前 i 位中含49的数量。
这前 i 位是从低位到高位的。那应该怎么转移呢?
d[i][0] = d[i-1][0] * 10 - d[i-1][1]; 表示长度为 i 的不含有49的数字的个数等于长度为 i - 1 的不含有49的数字的个数*10。
为什么呢?是这样的,d[i-1][0] * 10,这个包含了一种它不应该有的情况,那就是第 i 位是4,而第 i-1 位是9的,所以要减去。
dp[i][1] = dp[i-1][0];表示长度为 i 的并且不含有49同时最高位是9的数字的个数等于,长度为 i - 1 的不含有49的数字的个数,
因为只要在它的高一位加上一个9就可以了。所以是这样。
dp[i][2] = dp[i-1][2] * 10+ dp[i-1][1]; 表示长度为 i 的含有49的数字的个数等于,长度为 i - 1 的数字的个数*10,
再加上长度为 i - 1 的并且不含有49同时最高位是9的数字的个数,为什么呢,这是因为还得加上第 i-1 位是9,而第 i 位是4的情况。
这是预处理一下的。下面是具体计算时的DP。
1.首先加上 i-1位含有49的情况。也就是 ans += d[i-1][2] * a[i];
2.再看看是不是以前出现过49,如果出现了,就再加上现在没有49的情况,即 if(出现过) ans += d[i-1][0] * a[i];
3.如果没有出现过,就要判断这一位是不是大于4,如果大于4,就要再追加上长度为 i - 1 的不含有49但是最高位是9的数字的个数,
因为这个时候可以再这一位填4,肯定能和前组成一个49,因为它是大于4的。即 if(第 i 位大于4 && 没有出现49) ans += d[i-1][1];
4.那就是判断一下有没有49出现,如果有就标记,没有就算了,即 if(出现49了) ok = true;
最后就是在输入的时候,要计算n+1,而不是n,为什么呢?我的个人理解是这样的,如果不加1的话,那么最后一步,只标记了出现49了,
但是并没有把它计算上,所以是n+1。还要注意,这个输入数转化为字符时要把从下标1开始。
代码如下:
#include <iostream> #include <cstdio> #include <algorithm> #include <queue> #include <vector> #include <cstring> #include <map> #include <cctype> #include <cmath> using namespace std; typedef long long LL; const int maxn = 30; LL d[20][3], a[25];//用 long long void init(){//先处理一下 memset(d, 0, sizeof(d)); d[0][0] = 1; for(int i = 1; i <= 20; ++i){ d[i][0] = d[i-1][0] * 10L - d[i-1][1]; d[i][1] = d[i-1][0]; d[i][2] = d[i-1][2] * 10L + d[i-1][1]; } } LL solve(LL n){ int len = 0; while(n){//转化成字符串,当然也可以用sprintf,不过我不怎么用 a[++len] = n % 10L; n /= 10L; } a[len+1] = 0;//清空一下,以免出现巧合 LL ans = 0; bool ok = false; for(int i = len; i > 0; --i){ ans += d[i-1][2] * a[i]; if(ok) ans += d[i-1][0] * a[i];//出现过 if(a[i] > 4 && !ok) ans += d[i-1][1];//大于4,并且没有出现过 if(a[i] == 9 && a[i+1] == 4) ok = true;//标记出现过49 } return ans; } int main(){ init(); int T; cin >> T; LL n; while(T--){ scanf("%lld", &n);//注意输入输出 printf("%lld\n", solve(n+1));//注意是n+1 } return 0; }
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib> #include <cmath> #include <iostream> #include <cstring> #include <set> #include <queue> #include <algorithm> #include <vector> #include <map> #include <cctype> #include <cmath> #include <stack> #define freopenr freopen("in.txt", "r", stdin) #define freopenw freopen("out.txt", "w", stdout) using namespace std; typedef long long LL; typedef pair<int, int> P; const int INF = 0x3f3f3f3f; const double inf = 0x3f3f3f3f3f3f; const double PI = acos(-1.0); const double eps = 1e-8; const int maxn = 1e5 + 5; const int mod = 1e9 + 7; const int dr[] = {-1, 0, 1, 0}; const int dc[] = {0, 1, 0, -1}; const char *de[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"}; int n, m; const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; inline int Min(int a, int b){ return a < b ? a : b; } inline int Max(int a, int b){ return a > b ? a : b; } inline LL Min(LL a, LL b){ return a < b ? a : b; } inline LL Max(LL a, LL b){ return a > b ? a : b; } inline bool is_in(int r, int c){ return r >= 0 && r < n && c >= 0 && c < m; } LL dp[25][2], a[25]; LL dfs(int pos, bool is, bool ok){ if(!pos) return 1; LL &ans = dp[pos][is]; if(!ok && ans >= 0) return ans; LL res = 0, n = ok ? a[pos] : 9; for(int i = 0; i <= n; ++i){ if(is && 9 == i) continue; res += dfs(pos-1, i == 4, ok && i == n); } if(!ok) ans = res; return res; } LL solve(LL n){ int len = 0; while(n){ a[++len] = n % 10; n /= 10; } return dfs(len, false, true); } int main(){ memset(dp, -1, sizeof dp); int T; cin >> T; while(T--){ LL x; scanf("%lld", &x); printf("%lld\n", x-solve(x)+1); } return 0; }