题意
[L,R]区间内,各位数字组成的LIS(严格上升)长度为K的个数?
思路
如果还按照记忆化搜索的方式来写,那么我们过程中需要维护的是选了一个数字之后LIS的变化,我们应该怎么维护呢?
大家应该知道LIS的nlogn的求解方法。不会自行查找学习。
我们用dp数组的一维来表示我们选的这些数字的 LIS 情况,每当新加来一个数字时,我们就把他替换到第一个能替换的数字位置(LIS的更新方法),最后我们查一下状态数字有多少个1,LIS就是多长。
深搜过程中用一个变量allzero维护一下前导0的情况。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL L, R, K, dp[20][1<<10][15];
string num;
int update(int x, int state)
{
for (int i = x; i <= 9; i++)
if (state&(1<<i)) return (state^(1<<i))|(1<<x);
return state|(1<<x);
}
LL dfs(int pos, int state, bool limit, bool allzero)
{
if (pos == -1) return __builtin_popcount(state) == K;
if (!limit && dp[pos][state][K] != -1) return dp[pos][state][K];
LL ans = 0;
int E = limit?num[pos]-'0':9;
for (int i = 0; i <= E; i++)
ans += dfs(pos-1, (allzero&&(i==0)) ? 0 : update(i, state), limit&&(i==E), allzero&&(i==0));
if (!limit) dp[pos][state][K] = ans;
return ans;
}
LL solve(LL x)
{
num = to_string(x); reverse(num.begin(), num.end());
return dfs(num.size()-1, 0, 1, 1);
}
int main()
{
memset(dp, -1, sizeof(dp));
int T, CASE = 1; scanf("%d", &T);
while (T--)
{
scanf("%lld%lld%lld", &L, &R, &K);
printf("Case #%d: %lld\n", CASE++, solve(R)-solve(L-1));
}
return 0;
}
/*
1
123 321 2
*/