理解至上:数位dp(ybtoj-B数计数)

61 篇文章 0 订阅
54 篇文章 0 订阅

简要

数位dp,天下第一

最重要的应该有两个:
1.状态转移式的确定
2.试填法不断往后模拟
至今是唯一一道数位dp,究竟重要的是啥我其实也没有太多经验
半年之后的UPD:为什么要试填法啊!!记搜天下第一!
至少这道题提供了很好的方法与套路

题目描述

在这里插入图片描述

解析

dp定义:

pos:表示位数
res:表示膜13的余数
op:表示关于出现13的状态,其中:

op=0: 啥也没有
op=1:没有出现13但最高位是3(再来个1就ok啦!)
op=2:已经存在13了

dp[pos][res][op]就是表示符合上述状态的数的数量
具体状态转移见代码

试填法

比当前最高位(设为s)小的后面可以随便填
若本位填了s就不断向后模拟,注意op(这里op=1的定义是最后一位是1)和res随着填数的转移
大于s自然不能填啦
注意:这么填最后会填到n-1,所以要么一开始就把n+1,要么特判一下n

代码

(数位dp法)

#include<bits/stdc++.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=2e6+100;
const int M=2e6+100;
int dp[12][15][5],mi[15];
int a,m;
void Dp(){//预处理dp
	mi[0]=1;
	for(int i=1;i<=9;i++) mi[i]=mi[i-1]*10;
	dp[0][0][0]=1;
	for(int pos=1;pos<=10;pos++){//从后往前填 
		for(int i=0;i<=9;i++){
			for(int res1=0;res1<=12;res1++){
				int res2=(res1+13-i*mi[pos-1]%13)%13;
				if(i!=1&&i!=3) dp[pos][res1][0]+=dp[pos-1][res2][1];
				if(i!=3) dp[pos][res1][0]+=dp[pos-1][res2][0];
				if(i==3){
					dp[pos][res1][1]+=dp[pos-1][res2][0]+dp[pos-1][res2][1];
				}
				if(i==1) dp[pos][res1][2]+=dp[pos-1][res2][1];
				dp[pos][res1][2]+=dp[pos-1][res2][2];
			}
		}
	}
}
int solve(int n){//试填法
	int op=0,res1=0,ans=0;
	for(int pos=10;pos>=1;pos--){//从前往后填 
		int s=n/mi[pos-1];
		for(int i=s-1;i>=0;i--){//s以下自由填 
			int op2,resnow=(res1+i*mi[pos-1])%13,res2=(13-resnow)%13;//res2是后面需要的模数
			if(op==2||(op==1&&i==3)) op2=2;
			else if(i==1) op2=1;
			else op2=0;
			ans+=dp[pos-1][res2][2];
			if(op2) ans+=dp[pos-1][res2][1];
			if(op2==2) ans+=dp[pos-1][res2][0]; 
		}
		if(op!=2){
			if(op==1&&s==3) op=2;
			else if(s==1) op=1;
			else op=0;
		}
		res1=(res1+s*mi[pos-1])%13;
		n%=mi[pos-1];
	} 
//	if(op==2&&res1==0) ans++;
//这里的特判和下面的a+1有一个即可
	return ans;
}
int main(){
	Dp();
	while(scanf("%d",&a)!=EOF){
		printf("%d\n",solve(a+1));
	}
	return 0;
}
/*
13
100
200
1000
*/

thanks for reading!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值