HDOJ 4352 数位dp 题解
数位 dp 好套路啊,感觉只要会这个套路已经做出题目大半了。
题目前面全是废话,只要看题目描述的倒数第二行就行了。
大意是每次给定 ,求区间 内满足要求的数的个数,要求是这个数字的十进制表示的最长上升子序列等于 K,(数据组数 )。
比如 的 LIS 等于 , 的 LIS 等于 。
首先讲一下数位 dp 的套路,就是记忆化搜索(因为很多题目都是多次给出 ,记忆化搜索方便很多),然后从最高位往低位搜索,搜索时要维护 个量,一个是当前搜索到的位数 pos,一个是状态(状态可以用多个数表示),一个是 lim,表示是否被限制,最后一个是 lz,表示当前位的前一位是不是前导零(口胡的,听不懂大致看看代码就知道了)。
另外这题的状态用了一下状压,a 的第 b1,b2,...,bk 位是 1 代表当前最优秀的上升序列是 [b1,b2,...,bk],push 函数返回把 x 加入 a 后的状态。
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
#define mst(a,x) memset(a,x,sizeof(a))
typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
ll dp[20][1<<10][11],bit[20],k;
int push(int a,int x){
if((a>>x)&1)return a;
repeat(i,x+1,10)
if((a>>i)&1){
a^=(1<<i)^(1<<x);
return a;
}
a^=(1<<x);
return a;
}
ll dfs(int pos,int a,bool lim,bool lz){
if(pos==-1)return __builtin_popcount(a)==k;
ll &x=dp[pos][a][k];
if(!lim && x!=-1)return x;
ll ans=0;
int maxi=lim?bit[pos]:9;
repeat(i,0,maxi+1){
int nexta=push(a,i); //状态转移
if(i==0 && lz)nexta=a; //前导0就不要push了
ans+=dfs(pos-1,nexta,
lim && i==maxi,
lz && i==0);
}
if(!lim)x=ans; //不限制的时候才做存储
return ans;
}
ll solve(ll n){
int len=0;
while(n){
bit[len++]=n%10;
n/=10;
}
return dfs(len-1,0,1,1);
}
signed main(){
ll t=read();
mst(dp,-1);
repeat(cases,1,t+1){
ll l=read(),r=read(); k=read();
printf("Case #%d: %lld\n",
cases,solve(r)-solve(l-1));
}
return 0;
}