数位dp学习笔记

数位dp的标志:

  1. 要求统计满足一定条件的数的数量(即,最终目的为计数);
  2. 这些条件经过转化后可以使用「数位」的思想去理解和判断;
  3. 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;
  4. 上界很大(比如 1018),暴力枚举验证会超时。

数位dp的模板题 A - 不要62

实现非常的简单,设计一个dp[n][0/1] 选每一个数字的方案都记一下即可。

#include<bits/stdc++.h>
using namespace std;
int dp[15][2],vis[15][2];
int l,r,a[15];
int dfs(int len,int six,int lim){
	if(len==0)return 1;
	if(!lim&&vis[len][six])return dp[len][six];
	int res=0;
	for(int i=(lim?a[len]:9);i>=0;i--){
		if(i==4)continue;
		if(six&&i==2)continue;
		res+=dfs(len-1,i==6,lim&&i==a[len]);
	}
	if(!lim){
		vis[len][six]=1;
		dp[len][six]=res;
	}
	return res;
}
int solve(int x){
	if(x==0)return 1;
	int len=0;
	memset(a,0,sizeof a);
	while(x){
		a[++len]=x%10;
		x/=10;
	} 
	return dfs(len,0,1);
}
int main(){
	while(~scanf("%d%d",&l,&r)){
		if(!l&&!r)break;
		printf("%d\n",solve(r)-solve(l-1));
	}
	return 0;
}

C - Round Numbers

这一道题记的状态有所改变,可以记 dp[len][x][y] 表示在到 len 这一个位置时,0的数量和1的数量分别是 xy 当然如果你想省时间的话就可以记 dp[len][x] 代表两者的差,注意差可能是负数,需要加上长度。

D - B-number

这道题记一下前面是否有 13 除 13 的余数转移即可。

E - Balanced Number

这一个题就很有意思了。需要你枚举每一个点,作为平衡点的情况,记一下两边力矩之差即可。

时间复杂度 O(len2200)

F - XHXJ's LIS

一道好题,代码不麻烦并且有一定的思维量。

首先复习 LIS 的求法,O(n2) 的求法很简单 , 但 O(nlogn) 的求法用 LIS 的特点 ,即LIS长度越长,他的最末的数越大。

而且我们发现这里的数码只有 10 个,那我们是否可以使用状压的方法,来压一下每一个数是否在LIS

中出现,在每一次转移时,往st里添加这个数(当然这一个步骤需要预处理) 即可。

G - Beautiful numbers

数感题,很容易发现,其实这个各个数位的积上限时很少的,好像只有 2520,所以就跟 D - B-number 这一题一样了,就只要最后的时候对答案检查一下即可。

因为kmod2520mods=kmods

I - 吉哥系列故事——恨7不成妻

数学推导题,推一下 sqsumsumcnt 的关系即可。

K - Matches Puzzle Game

把等式改为加式,记dp[x][b][c][flag] 表示剩余火柴,b,c 是否到顶,是否有进位。

这样的数位dp 不算很明显,需要进行一定的转化才可以变成一般的数位dp。

#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int num[]= {6,2,5,5,4,5,6,3,7,6};
int n,mod,T;
int dp[505][2][2][2];
bool vis[505][2][2][2];
int dfs(int x,bool b,bool c,bool flag) {
	if(x<0)return 0;//不能堆叠了
	if(b&&c)return x==flag*2;
	if(vis[x][b][c][flag])return dp[x][b][c][flag];
	int res=0;
	if(b) {
		for(int i=0; i<=9; i++) {
			res+=dfs(x-num[i]-num[(i+flag)%10],1,0,(flag+i)/10);
			res%=mod;
			if(i)res+=dfs(x-num[i]-num[(i+flag)%10],1,1,(flag+i)/10);
			res%=mod;
		}
	} else if(c) {
		for(int i=0; i<=9; i++) {
			res+=dfs(x-num[i]-num[(i+flag)%10],0,1,(flag+i)/10);
			res%=mod;
			if(i)res+=dfs(x-num[i]-num[(i+flag)%10],1,1,(flag+i)/10);
			res%=mod;
		}
	} else {
		for(int i=0; i<=9; i++) {
			for(int j=0; j<=9; j++) {
				int tmp=i+j+flag;
				res+=dfs(x-num[i]-num[tmp%10]-num[j],0,0,tmp/10);
				res%=mod;
				if(i)res+=dfs(x-num[i]-num[tmp%10]-num[j],1,0,tmp/10);
				res%=mod;
				if(j)res+=dfs(x-num[i]-num[tmp%10]-num[j],0,1,tmp/10);
				res%=mod;
				if(i&&j)res+=dfs(x-num[i]-num[tmp%10]-num[j],1,1,tmp/10);
				res%=mod;
			}
		}
	}
	vis[x][b][c][flag]=1;
	return dp[x][b][c][flag]=res;
}
//时间复杂度O(500*3*2*100)
//分别表示在 x:剩余的木棍与 A需要的木棍的差 cnt: B C的暂停个数 (B C 等价) flag上一次有没有进位
//17 14 01+11=12
//细节 从小到大的堆放
signed main() {
	scanf("%lld",&T);
	int t=0;
	while(T--) {
		memset(vis,0,sizeof vis);
		memset(dp,0,sizeof dp);
		scanf("%lld%lld",&n,&mod);
		printf("Case #%lld: %lld\n",++t,dfs(n-3,0,0,0));
	}
	return 0;
}

对于数位dp 有三种求的量:最一般的就是求方案数,也有求和的,需要记一下每一个数的出现的个数,用这一个辅助数组进行最终的求值,当然还有求平方和的,这需要方案数,求和数组,共需要开三个数组才可以解决,详情可看I - 吉哥系列故事——恨7不成妻

posted @ 2025-02-09 12:48  hnczy  阅读(22)  评论(0编辑  收藏  举报