题目大意:
就是现在对于每个正整数可以将其每位视为一个数形成一个串, 那么这一组数就存在一个最长上升子序列, 对于每组给出的L, R (0 < L <= R <= 2^63 - 1) 求出在区间[L, R]中有多少个数在视作这样的一组数后最长上升子序列的长度是K
大致思路:
看数据范围很明显是数位DP, 考虑最长上升子序列这个子问题中常见的O(nlogn)的解法:
用dp[i]表示直到当前位, 在所有长度为i的上升子序列中结尾最小的是dp[i], 那么dp[i]满足单调性, 二分求解
那么在这个题中, 由于LIS的长度不可能超过10, 可以用状态压缩来存储当前LIS的状态, 如果状压的数的二进制中1的个数表示最大长度
而状态压缩中第i个1的位置p, 表示以所有上升子序列中长度为i的子序列结尾最小是p, 于是就可以进行状态转移了, 转移可以预处理出来
然后数位DP的时候, 记忆化即可
代码如下:
Result : Accepted Memory : 3388 KB Time : 124 ms
/* * Author: Gatevin * Created Time: 2015/8/3 15:59:02 * File Name: Sakura_Chiyo.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; /* * 用dp[bit][num][LIS]表示当前填到了第bit位, 填了num, 最长上升子序列的状况为LIS的情况有多少种 * 先预处理出LIS的转移trans[LIS][0~9] = ? * 然后在数位DP的时候记忆化搜索即可 * * 注意这里LIS状态压缩的含义: * 这个数的二进制第i个1的位置表示在当前的序列中, 长度为i的上升子序列结尾的数最小是多少 * 例如0110110010就表示当前LIS以8结尾, 当前长度为1, 2, 3, 4, 5的上升子序列最小的结尾分别是1, 2, 3, 4, 8 * 而这个二进制中的1的总个数就是LIS的长度 * 于是可以预处理LIS的转移 */ lint dp[20][1 << 10][11];//这里我的dp只是用来记忆化搜索而已..... int trans[1 << 10][10]; int ones[1 << 10];//1的个数 lint L, R; int K; void init() { memset(dp, -1, sizeof(dp)); for(int i = 0; i < (1 << 10); i++)//当前LIS状态i for(int j = 0; j < 10; j++)//来了一个数j { trans[i][j] = i; for(int k = j; k < 10; k++) if(i & (1 << (9 - k))) { trans[i][j] ^= (1 << (9 - k)); break; } trans[i][j] |= (1 << (9 - j)); } for(int i = 0; i < (1 << 10); i++) ones[i] = __builtin_popcount(i); return; } int bit[20];//记录边界的各位的值, bit[i]表示10^i的系数 lint dfs(int pos, int s, bool lim, bool zero)//当前填pos位, LIS状态为s, 是否有上界限制, 有无前导零 { if(pos == -1 && zero) return K == 1;//这个数只有一个0, LIS是0 if(pos == -1 && !zero) return ones[s] == K; if(!lim && !zero && dp[pos][s][K] != -1) return dp[pos][s][K]; lint ret = 0; if(zero) { ret += dfs(pos - 1, s, 0, 1);//继续前导零 for(int i = 1; i < bit[pos]; i++) ret += dfs(pos - 1, trans[s][i], 0, 0); if(lim) ret += dfs(pos - 1, trans[s][bit[pos]], 1, 0); else for(int i = max(1, bit[pos]); i < 10; i++) ret += dfs(pos - 1, trans[s][i], 0, 0); } else { for(int i = 0; i < bit[pos]; i++) ret += dfs(pos - 1, trans[s][i], 0, 0); if(lim) ret += dfs(pos - 1, trans[s][bit[pos]], 1, 0); else for(int i = bit[pos]; i < 10; i++) ret += dfs(pos - 1, trans[s][i], 0, 0); } if(!lim && !zero) dp[pos][s][K] = ret; return ret; } lint calc(lint num)//区间[0, num]之间满足条件的个数 { int len = 0; while(num) { bit[len++] = num % 10; num /= 10; } //一共有len位 return dfs(len - 1, 0, 1, 1); } int main() { init(); int T; scanf("%d", &T); for(int cas = 1; cas <= T; cas++) { scanf("%I64d %I64d %d", &L, &R, &K); printf("Case #%d: %I64d\n", cas, calc(R) - calc(L - 1)); } return 0; }