数位dp——求带回文的数字

题目链接

这是我写的第三道数位dp,前一道是windy数,其实本题和windy数差不多,只是需要判断的多了一位,在此我写一写关于这两题的思路

首先粗略的想,麻烦的有前导0,然而不难发现,某满位的数可以有上一个少一位的满位数转移而来,这也是“数位”,“dp”的第一次体现

对于任意非满位的数,我们设置数组nomax[i][j][k]为i位,最高位为j,次位为k的非回文数的个数,我们对j,k,ii从0~9枚举,从低位的数dp到高位的数,对每个符合要求的数都有

for(int i=2;i<1005;i++)
	{
		for(int j=0;j<10;j++)
		{
			for(int k=0;k<10;k++)
			{
				if(j==k) nomax[i][j][k]=-1;
				else if(i==2) nomax[i][j][k]=1;
				else
				{
					for(int ii=0;ii<10;ii++)
					{
						if(ii==j||ii==k) continue;
						nomax[i][j][k]+=nomax[i-1][k][ii];
						nomax[i][j][k]%=mod;
					}
				}
			}
		}
	}

这样我们就解决了比界限位小的所有回文数了

接下来就要求最大位的了

由于存在边界,我们使用一个free标记是否触碰到顶端,对于触碰到顶的我们要另外处理,我们从高位往低位dp,又题目可知我们需要从第n-2位开始操作,所以先遍历前两位可能的情况来进行dfs,若是小于最大边界,我们给个ture的free标记,表示某位可以任意取值,对于free的位,我们从0~9dp找到符合的数,对于触碰到边界的数,我们不能大于边界上该位的数,并且考虑将free的状态下传

大体思路就是这样,在计算的过程中,其实有很多重复的计算可以剪枝,如某数abc我正在枚举a,这时我已经把free的bc求出来了,在a++后再次遍历时只需要调出我们之前求过的数即可,这样我们就考虑一个记录数组mem[w][i][j]表示第w位前一位为j前两位为i的数量,当在free状态下得到了某个mem的值,我们便把它记录下来,下次遇见时在次使用即可

最后就是答案如何输出的问题了,我们在l~r间的回文数个数为(r-l+1)-dp(r)-dp(l)-(l是否为回文数),细节就是l,r巨大,我们可以对l,r不断的%mod,最后相减,减去dp结果在+mod,最后%mod就是答案了。

代码

#include <bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
ll nomax[1005][10][10]={0};//不是最高为的满位数的非回文数的数量 
ll mem[1005][10][10]={0};//剪枝 
int str[1005]={0};//数字 
ll dfs(int w,int i,int j,bool free)
{
	if(w<1) return 1;
	if(free&&mem[w][i][j]!=-1) return mem[w][i][j];
	ll res=0;
	int smax;
	if(!free) smax=str[w];//是否触碰边界 
	else smax=9;
	for(int k=0;k<=smax;k++)
	{
		if(i!=k&&j!=k) res+=dfs(w-1,j,k,free||k<smax),res%=mod;//有free的接下来都是free,触碰边界的如果k==smax则还是触碰边界 
	}
	if(free) mem[w][i][j]=res;//free则记录 
	return res;
}
ll dp(string x)
{
	ll n=x.size();
	if(n==1)//0-9都不是回文数 
	{
		ll s=x[0]-'0';
		return s+1;
	}
	for(int i=n;i>=1;i--)//由于位数由高往低,记录时注意与位数相符 
	{
		str[i]=x[n-i]-'0';
	}
	for(int i=0;i<1005;i++)
	for(int j=0;j<10;j++)
	for(int k=0;k<10;k++)
	mem[i][j][k]=-1;
	ll res=0;
	int imax=x[0]-'0';
	int jmax=x[1]-'0';
	//i没到达边界的情况 
	for(int i=1;i<imax;i++)
	{
		for(int j=0;j<10;j++)
		{
			if(i!=j) res+=dfs(n-2,i,j,1),res%=mod;
		}
	}
	//i到达边界的情况 
	for(int j=0;j<=jmax;j++)
	{
		if(imax!=j) res+=dfs(n-2,imax,j,j<jmax),res%=mod;//考虑free 
	}
	res+=10;//单位的数 
	for(int i=2;i<n;i++)
	for(int j=1;j<10;j++)//最高位不能是前导0 
	for(int k=0;k<10;k++)
	{
		if(j!=k) res+=nomax[i][j][k],res%=mod;
	}
	return res;
}
int main()
{
	string l,r;
	cin>>l>>r;
	for(int i=2;i<1005;i++)
	{
		for(int j=0;j<10;j++)
		{
			for(int k=0;k<10;k++)
			{
				if(j==k) nomax[i][j][k]=-1;
				else if(i==2) nomax[i][j][k]=1;
				else
				{
					for(int ii=0;ii<10;ii++)
					{
						if(ii==j||ii==k) continue;
						nomax[i][j][k]+=nomax[i-1][k][ii];
						nomax[i][j][k]%=mod;
					}
				}
			}
		}
	}
	ll res=0;
	res=dp(r)-dp(l);
	bool sign=0;//判断l是否为回文 
	for(int i=0;i<l.size();i++)
	{
		if(i+1<l.size()&&l[i]==l[i+1]) sign=1;
		if(i+2<l.size()&&l[i]==l[i+2]) sign=1; 
	}
	if(!sign) res++;
	res%=mod;
	//求出%下的r-l 
	ll a=0;
	ll s=1;
	for(int i=l.size()-1;i>=0;i--)
	{
		for(int j=0;j<l[i]-'0';j++)
		{
			a=(a+s)%mod;
		}
		s=(s*10)%mod;
	}
	ll b=0;
	s=1;
	for(int i=r.size()-1;i>=0;i--)
	{
		for(int j=0;j<r[i]-'0';j++)
		{
			b=(b+s)%mod;
		}
		s=(s*10)%mod;
	}
	//
	ll all=(b-a+1)%mod;
	printf("%lld\n",((all-res)%mod+mod)%mod);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值