数位DP 学习笔记

数位DP 顾名思义就是在数位上搞搞DP QAQ

之所以要引入数位的概念完全就是为了dp。数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。

DP 状态多半采用 dp[pos][..........] 来表示  ,其中POS 表示位数 ,......则是不同的题目的不同限制,dp[][]一般存放方案数

状态转移就按照题意与DP性质来枚举每一位的状态,然后将所有状态的总和都存到 pos 这一位中

然后  你就会想  好像这样搞不太对 

同一题目的不同组数据,可能会产生不一样的限制,这时枚举到pos位时再直接调用dp[pos]会产生错误

怎么办QAQ

想着在记忆化搜索中把这些限制带上

这就产生了limit 和 lead这两个骚东西

其中limit 表示 你所能枚举的最大范围

比如你要枚举 0 到 216 中的数

如果你第一位枚举了 2,那么就达到了最大范围(limit=1)

那么你第二位只能选择 0 和 1

如果你第一位枚举的是 1(limit=0)

那么你下一位从0 至 9 都可以选择(19X肯定比2XX小嘛)

lead 表示第pos之前的二进制数位都是0(就是前导零)

有的题目条件下前导零会对DP产生影响

所以搜索的时候把这个状态转移一下

模板如下

inline int dfs(int pos,int XXX,bool lead,bool limit){
	if (pos==-1)return XXX;//所有数位都已经枚举完毕 
	if (!lead&&!limit&&dp[pos][XXX]!=-1)return dp[pos][XXX];//记忆化 
	int ans=0;
	int up= limit? a[pos]:X;
	for (int i=0;i<=up;i++){//枚举当前数位所有可能性 
		if (lead&&i==0)ans+=dfs(pos-1,XXX,lead,limit & (i==a[pos]));//处理 前导零 
		else ans+=dfs(pos-1,dp转移,0,limit & (i==a[pos]));// 处理 limit  ::limit & (i==a[pos])
	}
	if (!limit&&!lead)dp[pos][sta]=ans;//记忆化 
	return ans;
}
inline int solve(int x){
	int pos=0;
	while (x){
		a[pos++]=x&1;//拆解每一位上的最大范围 
		x>>=1;
	}
	return dfs(pos-1,XXX,1,1);//这里 lead 和 limit 一定要是 1 QAQ 感性认知一下 
}

有些注意事项(蒟蒻认为是一些技巧QAQ):

~~~~~从 a 到 b 枚举,可以处理为 0 到 b 减去 0 到 (a-1)

~~~~~memset(dp,-1,sizeof(dp))

~~~~~某些求和可以转换为算差

~~~~~dp[pos]里面的值一般为数的固有属性而非题目上的一些限制属性(这是即使性质也是做题时要考虑到的点)

就这样吧(应该吧)



猜你喜欢

转载自blog.csdn.net/qq_40947548/article/details/79944119