数位DP入门总结 不要62+windy数总结

前段时间滚去搞了一波半期 然后学校又组织的五天西安游学 于是就一个星期都没有碰oi.....

终于回来了 学习数位DP  看了一下午 做了两道题 才感觉摸到一点火门

直接上两道题解吧

1.不要62

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2089

题意不多说 是中文都看得懂

为了方便阐述 我们把不含4,62的数称为吉利数  反之为非吉利数

这个题是在一个区间里找个数 所以我们考虑用前缀和的思想 只需找出1~m,1~n各自的个数 再相减就行了 于是这样只用关心上界了(命为work函数 work(x)可以求出【0,x】区间里的吉利数个数)

定义一下dp数组 这道题有什么明显的状态转移吗?注意到长度<=i位的吉利数个数 其实就是10个<=i-1位数的吉利数个数之和

(比如三位数即999    999就等于0~99,100~199,...900~999) 但是在这道题里要丢掉4 以及避免62的出现 这种细节后面再说

因此我们可以预处理出dp数组 由于后面有用 因此我们定义

dp[i][0]:<=i位的吉利数个数     dp[i][1]:长度为i 最高位是2的个数    dp[i][2]:<=i位的非吉利数个数 

work函数直接看注释吧 很详细的 不想多讲了

不要灰心 用纸对着一个数字推演下 会看懂的 我们都看了很久才懂


#include<bits/stdc++.h>
using namespace std;
int n,m,dp[15][3],a[12];

//dp[i][0]:<=i位的吉利数个数 
//dp[i][1]:长度为i 最高位是2的个数  
//dp[i][2]:<=i位的非吉利数个数 

void init()
{
	dp[0][0]=1;
	for(int i=1;i<=8;i++)	
	{
		dp[i][0]=dp[i-1][0]*9-dp[i-1][1]; //dp[i-1][0]*9即0,1,2,3,5,6,7,8,9 dp[i-1][1]是把62筛除 
		dp[i][1]=dp[i-1][0];	//相当于就是在i-1位时的最高位添一个2
	 	dp[i][2]=dp[i-1][2]*10+dp[i-1][0]+dp[i-1][1];	
//		dp[i-1][2]*10:先加上之前的非吉利数
// 		dp[i-1][0]:相当于加上i-1位最高位是2的数
//  	dp[i-1][1]:相当于在i-1位最高位是2的基础上添加6
	}
}
int work(int x)	//求0< <=x区间里的吉利数
{
	int rec=x,tot=0,ans=0;//tot是数的长度 ans存储非吉利数的个数 
	bool flag=false;
	while(x)
	{
		a[++tot]=x%10;
		x/=10; 
	}
	for(int i=tot;i>=1;i--)
	{
		ans+=dp[i-1][2]*a[i];	//先加上a[i]个i-1位数的非吉利数 比如231 分为2*100+31 先加上2个100里的不吉利数 
		if(flag)	//如果首位是4 或 62
		{
			ans+=dp[i-1][0]*a[i];   //flag==true是在上一位判断的 所以这里是加上上一位 注意这个地方是 吉利数 因为考虑到之前加过不吉利数了 
		}
		else
		{
			if(a[i]>4)	ans+=dp[i-1][0];	//包含了4 因此要加上后面的i-1位 他们都属于不吉利数了 
			if(a[i]>6)	ans+=dp[i-1][1];	//即62 注意这个地方dp[i-1][1]刚好是i-1位数首位是2开头的 和dp[i-1][0]不同 因此很巧妙 
			if(a[i+1]==6&&a[i]>2)	ans+=dp[i][1]; //后一位可能是2
			if(a[i]==4||(a[i+1]==6&&a[i]==2))	flag=true; 
		}
	}
	if(flag)	ans++;
	return  rec-ans;
}
int main()
{
	init();
	cin>>n>>m;
	cout<<work(m)-work(n-1);
	return 0; 
}

2.windy数

https://www.luogu.org/problemnew/show/P2657#sub

同样的 这也是在一个区间内找 采用与上题一样的策略

设dp[i][j]为长度为i中最高位是j的windy数的个数

方程 dp[i][j]=sum(dp[i-1][k]) 其中 abs(j-k)>=2(abs ->绝对值 在cmath里) 

这样的转移与上题相似 都是通过前一位的和转移过来的

work函数还是不讲了 看注释吧


#include<bits/stdc++.h>
using namespace std;
//设dp[i][j]为长度为i中最高位是j的windy数的个数
//方程 dp[i][j]=sum(dp[i-1][k]) 其中 abs(j-k)>=2 
int p,q,dp[15][15],a[15];
void init()
{
	for(int i=0;i<=9;i++)	dp[1][i]=1;	//0,1,2,3,4...9都属于windy数 
	for(int i=2;i<=10;i++)
	{
		for(int j=0;j<=9;j++)
		{
			for(int k=0;k<=9;k++)
			{
				if(abs(j-k)>=2)	dp[i][j]+=dp[i-1][k]; 
			}
		}
	}//从第二位开始 每次枚举最高位j 并找到k 使得j-k>=2 
}
int work(int x)	//计算<=x的windy数 
{
	memset(a,0,sizeof(a));
	int len=0,ans=0;
	while(x)
	{
		a[++len]=x%10;
		x/=10;
	}
	//分为几个板块 先求len-1位的windy数 必定包含在区间里的 
	for(int i=1;i<=len-1;i++)
	{
		for(int j=1;j<=9;j++)
		{
			ans+=dp[i][j];
		} 
	}
	//然后是len位 但最高位<a[len]的windy数 也包含在区间里 
	for(int i=1;i<a[len];i++)
	{
		ans+=dp[len][i];
	} 
	//接着是len位 最高位与原数相同的 最难搞的一部分 
	for(int i=len-1;i>=1;i--)
    {
    	//i从最高位后开始枚举 
        for(int j=0;j<=a[i]-1;j++)
		   {
		   	//j是i位上的数 
		   		if(abs(j-a[i+1])>=2)	ans+=dp[i][j]; //判断和上一位(i+1)相差2以上
				   //如果是 ans就累加 
		   } 
		if(abs(a[i+1]-a[i])<2)       break;
        if(i==1)   ans+=1;
    }
	return ans;
}
int main()
{
	init();
	cin>>p>>q;
	cout<<work(q)-work(p-1)<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值