HDU-4352 XHXJ's LIS 数位DP + 优化LIS + 状态压缩
一道很有教育意义的数位DP题目
题意
给定一个区间中,将区间的每一个数看成一个字符串,求这个区间内每个字符串的最大上升子序列等于(k)的个数。
[0 leq L leq R leq 2^{63} - 1\
1 leq k leq10
]
分析
拿到题目几乎没什么思路。因为涉及到LIS,但是数位DP的时候显然没办法存储这么多DP数组。
我们借鉴nlogn的LIS思想,考虑到最多只有10个数,不妨状态压缩用二进制的1表示该数计入LIS贡献,nlogn的LIS告诉我们每次只需找到第一个比当前数大的数并替换掉,就可以是等效的。
于是只需要照这个思路写,DP多增加一维状态就好了。
实际写的过程中,有以下问题:
看似状态只需要设置pos,state即可,但是这样的坏处是每次计算都需要初始化dp数组,对于T组数据显然无法承受,此时只需再增加一维k表示处理LIS = k的个数即可只需要在while外面初始化一次。
此题需要考虑前导0带来的影响,因为前导0不能算入LIS的贡献。
常用的剪枝:当前1的个数大于k时,直接return即可
代码
#include<bits/stdc++.h>
#define pii pair<long double,long double>
#define eps 1e-7
#define equals(a,b) (fabs(a - b) < eps)
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll MOD = 1e9 + 7;
ll rd(){
ll x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int kase;
int cur;
vector<int> v;
ll dp[25][(1 << 10) + 10][12];
ll dfs(int pos,int st,int lim){
int num = __builtin_popcount(st);
if(pos < 0) return num == cur;
if(num > cur) return 0;
if(!lim && ~dp[pos][st][cur]) return dp[pos][st][cur];
int l = lim ? v[pos] : 9;
ll res = 0;
for(int i = 0;i <= l;i++){
if(!(st | i)) {
res += dfs(pos - 1,0,lim && i == l);
continue;
}
int j;
int f = 0;
for(j = i;(1 << j) <= st;j++){
if((1 << j) & st) {
f = 1;
res += dfs(pos - 1,(st ^ (1 << j)) | (1 << i),lim && i == l);
break;
}
}
if(!f) res += dfs(pos - 1,st | (1 << j),lim && i == l);
}
if(!lim) dp[pos][st][cur] = res;
return res;
}
ll cal(ll x){
v.clear();
while(x){
v.push_back(x % 10);
x /= 10;
}
return dfs(v.size() - 1,0,1);
}
int main(){
int T = rd();
memset(dp,-1,sizeof dp);
while(T--){
ll a = rd();
ll b = rd();
int k = rd();
cur = k;
printf("Case #%d: %lld
",++kase,cal(b) - cal(a - 1));
}
}