最近刷了好几套数位dp的题目了,感觉对数位dp总算是有了一个清楚的认识了,现在就写个小结
数位dp其实就是对深搜的一个优化
保存一些深搜的状态,那么下次搜索到这个状态的时候就直接返回dp数组中保存的只即可
下面我们来看一道题
XHXJ's LIS
题意就是我们给一个范围 l,r要求l,r内,所有lis长度为k的数字的个数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
ll K,a[100],dp[25][1<<10][12];
using namespace std;
ll trans(ll data){
ll n(0);
while (data){
n++;
a[n]=data%10;
data/=10;
}
return n;
}
ll cal_sta(ll data){//找cal里有多少个1
ll n(0);
while(data){
n+=data%2;
data/=2;
}
return n;
}
ll update(ll sta,ll k){//根据现在的状态和k来更新状态
ll save;
if ((sta==0)&&(k==0))//对前导0的处理
return 0;
for (save=k;save<=10;save++)
if (sta&(1<<save))
return (sta-(1<<save))+(1<<k);
return sta+(1<<k);
}
ll DFS(ll poi,ll sta,ll limit){//poi是第poi位,sta是之前一位的状态哦,limit是有没有上界限制
if (poi==0)//这个就是一般的dfs,如果搜索到最后就返回就好了
return cal_sta(sta)==K;
if (!limit&&dp[poi][sta][K]!=-1)//如果这个状态之前被记录过了,就返回dp的值
return dp[poi][sta][K];
int up;//找到上界
if (limit) up=a[poi];
else up=9;
int nsta;
ll ans(0);
for (int k=0;k<=up;k++){
nsta=update(sta,k);//更新状态继续dfs
ans+=DFS(poi-1,nsta,limit&&k==a[poi]);
}
if (!limit)//更新dp的值
dp[poi][sta][K]=ans;
return ans;
}
ll cal(ll data){
int n=trans(data);
return DFS(n,(ll)0,1);
}
int main(){
ll t;
scanf("%lld",&t);
memset(dp,-1,sizeof(dp));
for (ll Case=1;Case<=t;Case++){
ll l,r;
scanf("%lld %lld %lld",&l,&r,&K);
printf("Case #%lld: %lld\n",Case,cal(r)-cal(l-1));
}
return 0;
}
//最后写一遍这个天杀的数位dp
/*传统的DFS做法就是递归找到最后一位,然后算他的LIS
那么什么状态下他的值就不会改变了呢
在第i位时ans数组一模一样的时候状态就是一样的
ans数组最多有十位嘛而且是严格递增的,感觉跟在此刻填进去的数字是多少没关系呀
二维就够了吧,其实三维有个好处,就是能搞一个k表示要求的长度,这样在处理t组数据的时候就能直接用啦
恩,接下来看看状态压缩dp怎么搞再来写这道题
晚上应该能搞出来
第一次状压一下*/
注意啦,一定要保证状态没有后效性哦,数位dp的关键就是找状态
很多状态的出错都跟前导0有关,所以一般都在dfs里面加一个bool表示有无前导0
这个题目的前导0我用三个参数就特殊处理掉了
所以就没加bool,找状态的时候一定要注意前导0哦
然后也没什么说的了,套路就是这样,
然后我发现通过加减来状压居然比位运算要快些,这个就很迷了
还有就是开始一部分开的是int,一部分开的是ll就wa了
然后全部开ll就A了,这个可能是有些inthe