HDU 4352 XHXJ's LIS ★(数位DP)
题意
求区间[L,R]内满足各位数构成的数列的最长上升子序列长度为K的数的个数。
思路
一开始的思路是枚举数位,最后判断LIS长度。但是这样的话需要全局数组存枚举的各位数字,同时dp数组的区间唯一性也被破坏了(我不知道MYQ10那题怎么被我用这种方法做对的。。。)
看了题解后发现了二进制缩位处理LIS的巧妙方法~~我们用一个长10位的二进制数state表示0~9之前是否出现过,而更新的时候也需要一点技巧:如果我们当前处理到的位数是i,那么我们就找state中不小于i的第一位非0位,把它置0,再把第i位置1。为什么这样处理呢?想想我们更新时的困难,就在于怎样维持state后面1的出现顺序要在前面1的后面,这样最后state中1的个数才表示LIS长度。呐当我们处理i时,对于i后面的1,就不符合出现顺序了,所以需要调整。但是我们发现对于紧接着i的那个1,它可以被i“替换”掉。因为此时以它为结尾和以i为结尾的LIS长度一样(因为是近邻着的下一个1),而它被i“替换”后又不影响了后面1的出现顺序了,把i当成原来的它就行了~
还有一点是,相对于每次询问都初始化dp数组(超时),更好的方法是给dp加一维[11]表示处理的K,这样只需在最开始初始化即可。
代码
[cpp]
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <set>
#include <stack>
#include <queue>
#define MID(x,y) ((x+y)/2)
#define MEM(a,b) memset(a,b,sizeof(a))
#define REP(i, begin, end) for (int i = begin; i <= end; i ++)
using namespace std;
typedef unsigned long long LL;
typedef vector <int> VI;
VI num;
LL dp[25][2][1050][11];
LL L, R;
int K;
int getnewstate(int x, int s) {
for (int i = x; i < 10; ++i)
if (s&(1<<i)) return (s^(1<<i))|(1<<x);
return s|(1<<x);
}
int get_dig_num(int state){
int res = 0;
for (int i = 0; i <= 9; i ++){
if (state & (1<<i))
res ++;
}
return res;
}
LL dfs(int pos, bool zero, int state, bool limit){
if (pos == -1){
return (get_dig_num(state) == K);
}
if (!limit && ~dp[pos][zero][state][K]) return dp[pos][zero][state][K];
int end = limit?num[pos]:9;
LL res = 0;
for (int i = 0; i <= end; ++ i){
bool next_zero = (zero && i==0);
res += dfs(pos-1, next_zero, next_zero?0:getnewstate(i, state), limit&&(i==end));
}
return limit?res:dp[pos][zero][state][K]=res;
}
LL solve(LL x){
num.clear();
while(x){
num.push_back(x%10);
x /= 10;
}
LL res = dfs(num.size()-1, 1, 0, true);
return res;
}
int main(){
int t;
MEM(dp, -1);
scanf("%d", &t);
REP(ca, 1, t){
scanf("%I64d %I64d %d", &L, &R, &K);
printf("Case #%d: %I64d
", ca, solve(R)-solve(L-1));
}
return 0;
}
[/cpp]