题目链接:XHXJ's LIS
初看这道题目首先回忆起LIS的O(nlogn)的做法,于是想到定义状态dp[pos][LIS*][k],pos代表当前数位,LIS*代表由前缀构造的LIS数组,k代表最长上升子序列的长度。
问题来了,一个数组肯定不能作为下标,所以我们要把这个数组转化成数字。
注意到数组的每个元素取值范围都是0~9,数组的长度最大为10,所以一个最简单的想法就是把数组转化成一个int类型的数字。
但是很明显需要的内存太大,还有什么办法呢?
因为是严格单调递增的子序列,所以LIS数组中的元素肯定是两两不同的,所有可能的情况总共有
这样一来只需要一个离散化就可以解决问题了,复杂度降低为18 * 1023 * 10。
还有一个小问题,如何离散化?
注意到1023 = 2^10 - 1,所以想到用一个10位的二进制数来表示0~9是否出现。
如果当前LIS数组为LIS[0] = 1, LIS[1] = 3, LIS[2] = 4,则对应的二进制数就为0101100000,这样一来就实现了离散化。
代码如下:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <map>
using namespace std;
typedef long long int LL;
LL dp[20][1200][20];
int Lis[20];
int bit[20];
int digit[20];
int Hash(int len)
{
int ret = 0;
for (int i = 0; i < len; i++)
ret += bit[Lis[i]];
return ret;
}
LL dfs(int pos, int len, int k, int lead, int limit)
{
if (!pos)
return len == k;
if (len > k)
return 0;
int code = Hash(len);
if (!limit && dp[pos][code][k] != -1)
return dp[pos][code][k];
int up = limit ? digit[pos] : 9;
LL ans = 0;
for (int i = 0; i <= up; i++)
{
int idx = lower_bound(Lis, Lis + len, i) - Lis;
int temp = Lis[idx];
Lis[idx] = i;
ans += dfs(pos-1, (lead&&!i)?0:len+(idx==len), k, lead&&!i, limit&&i==up);
Lis[idx] = temp;
}
return limit ? ans : dp[pos][code][k] = ans;
}
LL cal(LL n, int k)
{
int len = 0;
while (n)
{
digit[++len] = n % 10;
n /= 10;
}
return dfs(len, 0, k, 1, 1);
}
int main()
{
//freopen("test.txt", "r", stdin);
bit[0] = 1;
for (int i = 1; i <= 9; i++)
bit[i] = bit[i - 1] * 2;
memset(dp, -1, sizeof(dp));
int T, Case = 1;
scanf("%d", &T);
while (T--)
{
LL L, R;
int K;
scanf("%I64d%I64d%d", &L, &R, &K);
printf("Case #%d: ", Case++);
printf("%I64d\n", cal(R, K) - cal(L - 1, K));
}
return 0;
}