3316. 【BOI2013】非回文数字

64 篇文章 0 订阅
3 篇文章 0 订阅

Description

如果一个字符串从后往前读与从前往后读一致,我们则称之为回文字符串。当一个数字不包含长度大于1的子回文数字时称为非回文数字。例如,16276是非回文数字,但17276不是,因为它包含回文数字727。

你的任务是在一个给定的范围内计算非回文数字的总数。

Input

输入仅一行,包含两个整数a和b。

Output

输出仅一行,包含一个整数,表示a到b范围内(包括a和b)非回文数字的总数。

Sample Input

输入1:

123 321

输入2:

123456789 987654321

Sample Output

输出1:

153

输出2:

167386971

Data Constraint

25%的数据:b-a<=100 000.

100%的数据:0<=a<=b<=10^18

Solution

我们可以注意到,若一个串为回文串,必有以下两个条件中至少一个成立: 1:有两个相邻的数字相等。2:有两个相隔一个数字的数字相等。所以我们可以 用状态f(i, p, q),表示在 1~i 位,第 i 个数字为 q,第 i-1 个数字为 p 的合法方案数, 进行数位 DP。保证以上两个条件均不成立即可。时间复杂度O(lg max(?, ?) × 103 )。 (不要被这么小的 a,b 迷惑了~)

进行数位 DP 的一般方法

设 x 表示除了数位 i 以外的状态,本题是 p,q。若求区间[l,r]之间的答案,我 们用[0,r]的答案减去[0,l-1]的答案。下文“原串”表示这里的 r 或者 l-1。我们就 是要保证 DP 到的状态都小于等于原串。

方法 1

顺推,设状态 f(i,x,k),k=0(表示 1~i-1 位都和原串一样)或 1(有至少一位 小于原串)。枚举第 i 位数字 c,若 k=0,则 c≤原串第 i 位,否则 c≤9,若 i 为 数字的开头则保证 c>0。按情况转移即可。

方法 2

先逆推,设状态 f(i,x),表示 i 位数符合情况的所有答案,不必考虑和原串 的关系。注意逆推时 x 的定义可能稍有变化,比如本题 p,q 不再表示最右侧数字 而表示最左侧。然后顺序扫描一遍,同理枚举 c<原串第 i 位,然后对于所有 1~i-1 位与原串相同,第 i 位为 c 的答案都包含在 f(n-i,x)中。统计求和即可。

题解:

我们发现如果一个数中任意三个连续数字互不相同那么它一定不是回文数。因为两个相同的数字挨在一起或者它们隔着另一个数字那么它们就都是回文数字。

以下解法我们将数字倒着处理,因此第一位表示个位。

解法一:

设f[ i ][ j ][ k ]表示构造到第i位第i位的数为j,第i-1位的数为k,并且当前的数是小于原数的前i位的非回文数的方案。10代表前导0

g[ i ][ j ][ k ]表示构造到第i位第i位的数为j,第i-1位的数为k,并且当前的数是等于原数的前i位的非回文数的方案。

然后我们从高位往低位做

转移:

1.f[ i+1 ][ j ][ k ]->f[ i ][ l ][ j ] ( l,j,k不构成回文数,前导0的情况)

2.g[ i+1 ][第i+1位的数字][第i+2位的数字]->f[ i ][ j ][第i+1位的数字] ( 不构成回文)

3.g[ i+1 ][第i+1位的数字][第i+2位的数字]->g[ i ][第i位的数字][第i+1位的数字] (不构成回文)

答案就是Sum(f[1][ i ][ j ])+g[1][第1位的数字][第2位的数字]。

解法二:

设f[ i ][ j ][ k ][ 0/1 ][ 0/1 ]表示构造到第i位,第i位数是j,第i-1位数是k,构造的数是否大于原数的前i位,是否是回文数,是或不是回文数的方案。

从低位往高位做。

我们不用管f[1]了,直接从f[2]开始

f[ i+1 ][ r ][ j ][ s ][ t ]+=f[ i ][ j ][ k ][ o ][ q ] 更新一下,自己推,直接转。

最后答案就是总的数减去回文数个数 ,即 x-(Sum( f[ 位数小于i ][ 任意不为0的数 ][ 0/1 ][1] )+Sum( f[ 位数等于i ][ 任意不为0的数 ][ 0 ][ 1 ] ))。

Code1

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int g[22][11][11],p[20],l;
ll f[22][11][11],a,b,ans;
ll sol(ll x){
	if(x<10) return x+1;
	l=ans=0;while(x){p[++l]=x%10;x/=10;}
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	g[l-1][p[l-1]][p[l]]=(p[l-1]!=p[l]);
	for(int i=1;i<=10;i++) f[l-1][i][10]=1;
	for(int i=0;i<p[l-1];i++) f[l-1][i][p[l]]=(i!=p[l]);
	for(int i=0;i<=9;i++)
		for(int j=1;j<p[l];j++) f[l-1][i][j]=(i!=j);
	for(int i=l-2;i>0;i--){
		for(int j=0;j<p[i];j++){
			if(j!=p[i+1]&&j!=p[i+2]) f[i][j][p[i+1]]+=g[i+1][p[i+1]][p[i+2]];
		}
		if(p[i]!=p[i+1]&&p[i]!=p[i+2]) g[i][p[i]][p[i+1]]=g[i+1][p[i+1]][p[i+2]];
		for(int j=0;j<=10;j++){
			for(int k=0;k<=10;k++){
				if(j==k&&j!=10) continue;
				for(int l=0;l<=9;l++){
					if(l==0&&j==10) continue;
					if(j==l||k==l) continue;
					f[i][l][j]+=f[i+1][j][k];
				}
			}
		}
		f[i][10][10]=1;
	}
	for(int i=0;i<=9;i++){
		for(int j=0;j<=10;j++) if(i!=j) ans+=f[1][i][j];
	}
	return ans+g[1][p[1]][p[2]];
}
int main(){
	scanf("%lld%lld",&a,&b);
	if(b<10) printf("%d\n",b-a+1);
	else if(a<10) printf("%lld\n",sol(b)-a+1);
	else printf("%lld\n",sol(b)-sol(a-1));
	return 0;
}

Code2

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define F(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int l,p[20];
ll ans,f[20][11][11][2][2],a,b;
ll sol(ll x){
	memset(f,0,sizeof(f));
	l=0;ans=0;ll i=x;
	while(i){p[++l]=i%10;i/=10;}
	F(i,0,9){
		F(j,0,9){
			int k=(j>p[2]||j==p[2]&&i>p[1]),l=(i==j);
			f[2][j][i][k][l]=1;
		}
	}
	F(i,2,l-1)
		F(j,0,10)
			F(k,0,10)
				F(o,0,1)
					F(q,0,1)
						if(f[i][j][k][o][q]){
							F(r,0,9){
								int s=o,t=(q||r==j||r==k);
								if(r>p[i+1]) s=1;if(r<p[i+1]) s=0;
								f[i+1][r][j][s][t]+=f[i][j][k][o][q];
							}
						}
	F(i,2,l-1)
		F(j,1,9) 
			F(k,0,9) ans+=f[i][j][k][0][1]+f[i][j][k][1][1];
	F(i,1,p[l]){
		F(j,0,9) ans+=f[l][i][j][0][1];
	}
	return x-ans;
}
int main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	scanf("%lld%lld\n",&a,&b);
	printf("%lld\n",sol(b)-sol(a-1));
	return 0;
}


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/94769856

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值