【题意】
将一个数x当成一个数串,对其做最长上升子序列。记结果为 a x a_x ax。对一个区间 [ l , r ] [l,r] [l,r],求有多少个数 x x x,使 a x = k a_x=k ax=k。
【分析】
先提一提LIS的
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的做法:
维护一个数列
t
a
i
l
[
i
]
tail[i]
tail[i],表示长度为i的LIS的最后一个元素最小的数。当新加入一个数
a
[
i
]
a[i]
a[i]时,对
t
a
i
l
tail
tail二分。若有一个位置,使
t
a
i
l
[
m
i
d
]
<
a
[
i
]
<
t
a
i
l
[
m
i
d
+
1
]
tail[mid]<a[i]<tail[mid+1]
tail[mid]<a[i]<tail[mid+1],给
t
a
i
l
[
m
i
d
+
1
]
tail[mid+1]
tail[mid+1]赋值为
a
[
i
]
a[i]
a[i]。否则,在
t
a
i
l
tail
tail的最末尾将
a
[
i
]
a[i]
a[i]插入。答案即为
t
a
i
l
tail
tail所含元素的数量。
然后想到:设
f
(
i
,
S
)
f(i,S)
f(i,S)表示当前做到第
i
i
i位,
t
a
i
l
tail
tail的状态为S。然后按题意转移即可。
但有一个小问题:如何表示S?若直接将
t
a
i
l
tail
tail塞进状态,你不MLE谁MLE。
由于
t
a
i
l
tail
tail数组单调递增,故
0
9
0~9
0 9在数组中只会出现一次。所以,不妨把
S
S
S改造成二进制,第
i
i
i位表示
i
i
i是否在
t
a
i
l
tail
tail数组里面。然后每新加入一个数
x
x
x,找到一个等于
1
1
1,但位数比它高的位
s
s
s,将
s
s
s位置
0
0
0,再将
x
x
x位置
1
1
1。若
x
x
x为当前最高位,直接将
x
x
x位置
1
1
1即可。
这个过程可以用位运算实现。具体见代码。
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int mn = 20;
int d[mn], n;
ll f[mn][1 << 10][11];
inline int lenth(int S)
{
int ret = 0;
while(S)
ret += (S & 1), S >>= 1;
return ret;
}
inline int push(int S, int x)
{
for(int i = x; i < 10; i++)
if(S & (1 << i))
return (S ^ (1 << i)) | (1 << x);
return S | (1 << x);
}
inline ll dp(int pos, int S, bool f0, bool lim)
{
if(!pos)
return lenth(S) == n;
if(!lim && ~f[pos][S][n])
return f[pos][S][n];
int m = lim ? d[pos] : 9;
ll ans = 0;
for(int i = 0; i <= m; i++)
ans += dp(pos - 1, (f0 && i == 0) ? 0 : push(S, i), f0 && i == 0, lim && i == m);
if(!lim)
f[pos][S][n] = ans;
return ans;
}
inline ll calc(ll num)
{
int cnt = 0;
while(num)
d[++cnt] = num % 10, num /= 10;
return dp(cnt, 0, 1, 1);
}
int main()
{
int t;
ll l, r;
scanf("%d", &t), memset(f, -1, sizeof f);
for(int i = 1; i <= t; i++)
scanf("%lld%lld %d", &l, &r, &n), printf("Case #%d: %lld\n", i, calc(r) - calc(l - 1));
}