HDU - 4352 XHXJ‘s LIS(数位dp+状态压缩LIS)

题目链接: 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;
}

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值