【Ybt OJ】[动态规划 第3章] 数位DP [前半章]

57 篇文章 1 订阅
45 篇文章 0 订阅

「 「 动态规划 」 」 3 3 3章 数位 D P ( DP( DP( 3 3 3 ) ) )
目录:

A.B数计数
B.区间圆数
C.数字计数

A . A. A. 例题 1 1 1 B B B数计数

在这里插入图片描述

分析:

p o s pos pos表示位数 m o d mod mod表示膜的余数 k k k表示出现 13 13 13的状态
k = 0 k=0 k=0 表示没有
k = 1 k=1 k=1 表示没有出现 13 13 13 但最高位为 3 3 3 这时找到 1 1 1即可
k = 2 k=2 k=2 表示出现 13 13 13

f p o s , m o d , k f_{pos,mod,k} fpos,mod,k为符合条件的数的数量
然后就试填法 s s s为当前 p o s pos pos位上的数 要填只能填 < s <s <s
这样只能模拟到 n − 1 n-1 n1 所以最后特判 n n n或把 n + 1 n+1 n+1

CODE:

#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=15;
int f[N][N][4],power[N],n;
void DP()
{
	f[0][0][0]=1;
	for(int pos=1;pos<=10;pos++)
		for(int i=0;i<=9;i++)
		for(int mod=0;mod<13;mod++)
		{
			int mod2=(mod+13-i*power[pos-1]%13)%13;
			if(i!=1&&i!=3) f[pos][mod][0]+=f[pos-1][mod2][1];
			if(i!=3) f[pos][mod][0]+=f[pos-1][mod2][0];
			if(i==3) f[pos][mod][1]+=f[pos-1][mod2][0]+f[pos-1][mod2][1];
			if(i==1) f[pos][mod][2]+=f[pos-1][mod2][1];
			f[pos][mod][2]+=f[pos-1][mod2][2];
		}
} 
int query(int x)
{
	int k=0,mod=0,ans=0;
	for(int pos=10;pos>=1;pos--)
	{
		int s=x/power[pos-1];
		for(int i=s-1;i>=0;i--)  //s以下填数
		{
			int k2,mod2=(mod+i*power[pos-1])%13,mod3=(13-mod2)%13;
			if(k==2||(i==3&&k==1)) k2=2;
			else if(i==1) k2=1;
			else k2=0;
			ans+=f[pos-1][mod3][2];
			if(k2==2)
				ans+=f[pos-1][mod3][0]+f[pos-1][mod3][1];
			if(k2==1) 
				ans+=f[pos-1][mod3][1];
		}
		if(k!=2)
		{
			if(s==3&&k==1) k=2;
			else if(s==1) k=1;
			else k=0;
		}
		mod=(mod+s*power[pos-1])%13;
		x%=power[pos-1];
	}
	return ans;
}
int main()
{
	power[0]=1;
	for(int i=1;i<=9;i++)
		power[i]=power[i-1]*10;
	DP();
	while(scanf("%d",&n)!=EOF){
		printf("%d\n",query(n+1));
	}
	
	return 0;
}

B . B. B. 例题 2 2 2 区间圆数

在这里插入图片描述
洛谷 l i n k link link

分析:

[ l , r ] [l,r] [l,r]的个数 就用 [ 1 , r ] [1,r] [1,r]的个数减 [ 1 , l − 1 ] [1,l-1] [1,l1]的个数
f p o s , i f_{pos,i} fpos,i表示二进制下填到第 p o s pos pos位 此时这个二进制数有 i i i 0 0 0 ( ( (包含前导 0 ) 0) 0)
那么有 f p o s , i = f p o s − 1 , i − 1 + f p o s − 1 , i f_{pos,i}=f_{pos-1,i-1}+f_{pos-1,i} fpos,i=fpos1,i1+fpos1,i
注意前导 0 0 0会对结果影响 就要特判

CODE:

#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
int l,r,f[35][35];
void DP()
{
	f[0][0]=1;
	for(int pos=1;pos<=31;pos++)
	{
		f[pos][0]=1;
		for(int i=1;i<=pos;i++)
			f[pos][i]=f[pos-1][i-1]+f[pos-1][i];
	}
}
int query(int x)
{
	if(x==0) return 0;
	int tot=0,s0=0,s1=0,ans=0,s[35];
	while(x){
		s[++tot]=x&1;
		x>>=1;
	}
	for(int pos=tot;pos>=1;pos--)
	{
		if(s[pos]&&pos^tot)  //判首位
		{
			s0++;
			for(int i=0;i<pos;i++)
				if(i+s0>=tot-s0-i)
					ans+=f[pos-1][i];
			s0--;
		}
		if(pos^tot)
			for(int i=0;i<pos;i++)
				if(i>=pos-i) ans+=f[pos-1][i];
			s0+=!s[pos];
			s1+=s[pos];
	}
	if(s0>=s1) ans++;
	return ans;
}
int main()
{
	DP();
	scanf("%d%d",&l,&r);
	printf("%d",query(r)-query(l-1));
	return 0;
}

C . C. C. 例题 3 3 3 数字计数

在这里插入图片描述
洛谷 l i n k link link

分析:

f i f_i fi表示在 i i i位数字下 每个数有多少个
f i = f i − 1 × 10 + 1 0 i − 1 f_i=f_{i-1}\times 10+10^{i-1} fi=fi1×10+10i1 设数字 E f g h Efgh Efgh 即首位为 E E E
如果只有 E 000 E000 E000 那个数就是后 3 3 3位个数 E × f 3 E\times f_3 E×f3 再加上首位个数 1 0 3 10^3 103

再考虑 如果是 E f g h Efgh Efgh 要怎么做
其实也可以看成 首位为 f f f 首位为 g g g 首位为 h h h 这时与前面的 E E E没有关系
这样就把数每一位拆出来 做上面的过程
关于 0 0 0 要处理前导 0 0 0 共出现 10 × ( i − 1 ) + 10 × ( i − 2 ) + . . . + 10 10\times (i-1)+10\times (i-2)+...+10 10×(i1)+10×(i2)+...+10 0 0 0的统计要减去这个值
[ l , r ] [l,r] [l,r]的个数 就是 [ 1 , r ] − [ 1 , l − 1 ] [1,r]-[1,l-1] [1,r][1,l1]

CODE:

#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=25;
ll l,r,power[N],f[N],Cntl[N],Cntr[N];
void DP(ll x,ll ans[])
{
	ll num[N];
	int tot=0;
	while(x)
	{
		num[++tot]=x%10;
		x/=10;
	}
	for(int pos=tot;pos>=1;pos--)
	{
		for(int i=0;i<=9;i++)
			ans[i]+=f[pos-1]*num[pos];
		for(int i=0;i<num[pos];i++)
			ans[i]+=power[pos-1];
		ll num2=0;
		for(int i=pos-1;i>=1;i--)
			num2=num2*10+num[i];
		ans[num[pos]]+=num2+1;
		ans[0]-=power[pos-1];
	}
}
int main()
{
	scanf("%lld%lld",&l,&r);
	power[0]=1;
	for(int i=1;i<=15;i++)
	{
		f[i]=f[i-1]*10+power[i-1];
		power[i]=power[i-1]*10;
	}
	DP(l-1,Cntl);
	DP(r,Cntr);
	for(int i=0;i<=9;i++)
		printf("%lld ",Cntr[i]-Cntl[i]);
	
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值