http://acm.hdu.edu.cn/showproblem.php?pid=2089
dp[i][j]表示长度为i的数字中,开头的数字是j的时候,有多少种合法的情况。
预处理挺好理解,关键是那个统计,比较难。
比如我统计的是数字: 456
那么,从高到低枚举。先枚举第一位,4、再枚举5、....
那么,< 4 的,长度是3位的合法数字,都应该算做贡献。就是ans += dp[3][0...3]
dp[3][1]好理解,就是100、110、....199那些。
dp[3][2]那些同理,不处理到dp[3][4]也好理解,因为dp[3][4]包含了499那些,就是超越范围了。
然后,dp[3][0]是什么呢?其实就是0xx,其中,xx就是00---99
所以也就是所有的1位数和2位数的合法情况。(这个回归下dp预处理就知道了)
而后来的枚举下一位的5,dp[2][0...4]是同理的,但是其是和第一位的4结合的,也就是dp[2][0]包括了所有1位数的合法情况,然后组合成40x。
所以后面的枚举其实这是为了和上一位组合成合法情况。所以当其中某些数字破坏了条件,例如出现了4,或者已经出现了62,就要提前break
还有需要注意的是处理不到n这个数字的,因为都是枚举每一位的-1那个大小,所以只需要把他们 + 1即可。
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <assert.h> #define IOS ios::sync_with_stdio(false) using namespace std; #define inf (0x3f3f3f3f) typedef long long int LL; #include <iostream> #include <sstream> #include <vector> #include <set> #include <map> #include <queue> #include <string> #include <bitset> int dp[10][20]; void init() { dp[0][0] = 1; for (int i = 1; i <= 7; ++i) { for (int j = 0; j <= 9; ++j) { if (j == 4) continue; for (int k = 0; k <= 9; ++k) { if (k == 4 || j == 6 && k == 2) continue; dp[i][j] += dp[i - 1][k]; } } } } char str[222]; int calc(int val) { int lenstr = 0; while (val / 10 > 0) { str[++lenstr] = val % 10 + '0'; val /= 10; } str[++lenstr] = val + '0'; str[lenstr + 1] = '\0'; int ans = 0; for (int i = lenstr; i >= 1; --i) { for (int j = 0; j <= str[i] - '0' - 1; ++j) { if (j == 2 && str[i + 1] == '6') continue; // if (j == 4) continue; ans += dp[i][j]; } //后来枚举的,都是和前面的哪一位结合 //比如456,就是4XX,然后45X if (str[i] == '4') break; if (str[i] == '2' && str[i + 1] == '6') break; } // cout << ans << endl; return ans; } int n, m; void work() { // cout << dp[5][4] << endl; cout << calc(m + 1) - calc(n) << endl; } int main() { #ifdef local freopen("data.txt", "r", stdin); // freopen("data.txt", "w", stdout); #endif init(); while (scanf("%d%d", &n, &m) != EOF && (n + m)) work(); return 0; }
复习了一下,下面是新的总结
HDU 2089 不要62 int dp[10][20]; // dp[i][j]表示长度是i的数字中,以j开头的合法情况 void init() { dp[0][0] = 1; for (int i = 1; i <= 7; ++i) { //枚举数字的长度是多少位 for (int j = 0; j <= 9; ++j) { //枚举长度是i的开头数字 if (j == 4) continue; //4是不合法情况,跳过了,所以dp[i][4] = 0; for (int k = 0; k <= 9; ++k) { //枚举长度是i - 1的开头数字 if (k == 4 || j == 6 && k == 2) continue; //其实这个k = 4不跳过也一样,= 0 dp[i][j] += dp[i - 1][k]; } } } //dp[i][0] += dp[i - 1][0] + dp[i - 1][1] ... + dp[i - 1][9]; } 注意的是dp[i][0]的意义,dp[i][0]表示长度是i的,以0开头的合法数字。也就是0XX的形式,所以是包含了000---099,也就是所有合法的1位数和2位数之和。那000是什么?不管了,这个是多余的,也可以看作是合法的一位数,虽然题目的区间不包含000,但是R – L的时候会同时抵消这个多余的计数。 比如我统计的是数字: 456 那么,从高到低枚举。先枚举第一位,4、再枚举5、....那么,< 4 的,长度是3位的合法数字,都应该算做贡献。就是ans += dp[3][0...3],dp[3][1]好理解,就是100、110、....199那些。 dp[3][2]那些同理,不处理到dp[3][4]也好理解,因为dp[3][4]包含了499那些,就是超越范围了。而后来的枚举下一位的5,dp[2][0...4]是同理的,但是其是和第一位的4结合的,也就是dp[2][0]包括了所有1位数的合法情况,然后组合成40X。所以后面的枚举其实这是为了和上一位组合成合法情况。所以当其中某些数字破坏了条件,例如出现了4,或者已经出现了62,就要提前break。还有需要注意的是处理不到n这个数字的,因为都是枚举每一位的-1那个大小,所以只需要把他们 + 1即可。 int calc(int val) { //calc(R + 1) - calc(L + 1 - 1) int lenstr = 0; while (val / 10 > 0) { str[++lenstr] = val % 10 + '0'; val /= 10; } str[++lenstr] = val + '0'; str[lenstr + 1] = '\0'; int ans = 0; for (int i = lenstr; i >= 1; --i) { for (int j = 0; j <= str[i] - '0' - 1; ++j) { if (j == 2 && str[i + 1] == '6') continue; //62X是不合法的,要跳过 //那么j = 4呢?不跳过?其实是一样的,因为预处理的时候dp[i]][4] = 0的。 ans += dp[i][j]; } //后来枚举的,都是和前面的哪一位结合,比如456,就是4XX,然后45X if (str[i] == '4') break; //4XX,下一次枚举就不合法了 if (str[i] == '2' && str[i + 1] == '6') break; } return ans; }