题目链接: XHXJ’s LIS
大致题意:
又臭又长的题面
求给定区间内长度为k的最长上升子序列的个数
解题思路:
涉及数位dp,状态压缩,LIS
1.LIS O(nlogn)算法
定义f[len],初始len=0,a[]={4,2,6,3,1,5};
if(a[i]>f[len]) ++len,f[len]=a[i];
else 从1~len中找到一个位置k,满足f[k-1]<a[i]<f[k],令f[k]=a[i];
总的来说就是找到第一个比自己大或相等的数,将它删除,把自己放上去。如果没有比自己大的数,就把自己放到最后
f数组里保存的值是具有单调性的,所以查找k的过程我们就可以用二分的方法得到
所以复杂度就为O(nlogn).
2.接下来就是用二进制状态压缩求LIS
0~9只有十个数,LIS最大长度不过为10。
我们用10位二进制数来表示9~0的数字是否出现过。
比如上一个pos(数位dp中,枚举第pos个位置)的状态是0101000101表示8,6,2,0,出现过。
到当前pos时,枚举0~9。(在最初的状态的基础上改变)
0:状态不改变
1:0101000011,将2位置置0,1 位置置1
2: 状态不改变
3:0100001101,将6位置置0,3 位置置1
4:0100010101,将6位置置0,4 位置置1
5:0100100101,将6位置置0,3 位置置1
6:状态不改变
7:0011000101,将8位置置0,7 位置置1
8:状态不改变
9:1101000101, 将9位置置1
总的来说就是找到第一个比当前枚举的数大或相等的位置,将它置零,自己置1.如果没有就把自己的位置置1
二进制中1的个数就是LIS的长度
3.特别的说一下LIS更新状态
state(当前状态)&1<<i 是比较有没有(2的i次幂)这一大小的的值,即有没有数字i,没有的话i++,继续向前找,如果有的话,s^(1<<i ) 是让第i位置为0,要是没有的话,直接stste|(1<<pos)(即pos为最大的放入数字更新)。
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll f[30][1 << 10][12];
ll l, r, k;
int a[30];
int count(int state) { //统计当前二进制状态由多少个1
int cnt = 0;
while (state) {
cnt += (state & 1);
state >>= 1;
}
return cnt;
}
int New(int state, int pos) { //LIS更新状态
for (int i = pos; i <= 9; ++i) {
if (state & (1 << i)) { state ^= (1 << i); break; }
}
return state | (1 << pos);
}
//limit是限制当前位的选择,对于首位,从0到a[len-1],其他位都是从0到9
//zero是判断前导零
ll dfs(int pos, int state, bool limit, bool zero) {
if (pos == -1)return count(state) == k;
if (!limit && f[pos][state][k] != -1)return f[pos][state][k]; //记忆化搜索
ll ans = 0;
int end = limit ? a[pos] : 9;
for (int i = 0; i <= end; ++i) {
ans += dfs(pos - 1, zero && i == 0 ? 0 : New(state, i), limit && i == end, zero && i == 0);
}
if (!limit)f[pos][state][k] = ans;//记忆化搜索
return ans;
}
ll dp(ll n) {
int len = 0;
while (n)a[len++] = n % 10, n /= 10;
return dfs(len - 1, 0, 1, 1);
}
int main() {
memset(f, -1, sizeof f);
int t; cin >> t;
for (int i = 1; i <= t; ++i) {
cin >> l >> r >> k;
printf("Case #%d: %lld\n",i, dp(r) - dp(l - 1));
}
return 0;
}