题目大意:
就是现在对于每个正整数可以将其每位视为一个数形成一个串, 那么这一组数就存在一个最长上升子序列, 对于每组给出的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;
}