FZU 2109 Mountain Number_数位DP

原题:

Problem Description

One integer number x is called "Mountain Number" if:

(1) x>0 and x is an integer;

(2) Assume x=a[0]a[1]...a[len-2]a[len-1](0≤a[i]≤9, a[0] is positive). Any a[2i+1] is larger or equal to a[2i] and a[2i+2](if exists).

For example, 111, 132, 893, 7 are "Mountain Number" while 123, 10, 76889 are not "Mountain Number".

Now you are given L and R, how many "Mountain Number" can be found between L and R (inclusive) ?

 Input

The first line of the input contains an integer T (T≤100), indicating the number of test cases.

Then T cases, for any case, only two integers L and R (1≤L≤R≤1,000,000,000).

 Output

For each test case, output the number of "Mountain Number" between L and R in a single line.

 Sample Input

3
1 10
1 100
1 1000

 Sample Output

9
54
384



题意+题解:

非常基础的一道i数位DP的题目,也是我写的第一道数位DP,各种无力。。。。

原题是说 有一种数叫做 Mountain Number,当其偶数位大于等于两边,奇数位小于等于两边时。

然后有K次询问。K<100。问L到R区间内有多少个Mountain Number。 L,R<10^9

显然无法用正常的枚举,10^9,即使是O(n)也要超。

这种询问区间内符合条件的数的个数是典型的数位DP的问法。说实话,我也是稀里糊涂就A了,因为这题要考虑的条件比较少,初值为0的情况下即使多进行了多余的DP转移也不会影响答案。。。所以我目前对数位DP的理解依旧不清晰。


回到此题。典型的做法是分别求[0,L]和[0,R]内的符合条件的个数,再相减,注意判断边界值的问题。这样进行DP时,不用考虑下界的问题,比较方便。

定义状态 dp[i][j][0/1] 表示当前第i位上,选择为j时所能求得的所有方案,[0/1]表示之前的上一位有没有触顶,因为要考虑上届的问题。

所以i只有10表示数位个数,0<=j<=9,加之转移上一位也有[0,9],所以一个数求得结果只有10*10*2,非常快

转移:

首先判断i是奇数位还是偶数位

如果是奇数位;

if(j>=k)//表示第i位比第i-1位的值大,满足条件

dp[i][j][0] += dp[i-1][k][0];

if(k== num[i-1])//表示k是上一位的上界值

dp[i][j][0]+=dp[i-1][k][1];

if(j==num[i])

dp[i][j][1]+=dp[i-1][k][1];

如果是偶数位,那就判断j<=k时,下面不变。


最后答案就是 dp[最终位][][]的所有j 0/1 加和

代码:

#include<cstdio>
#include<cstring> 
#include<vector>
#include<algorithm>
using namespace std;
int dp[20][10][2];//i表示在第i位,j表示i为是几,0表示未封顶,1表示封顶
int num[20];
int len;
int find_dp(int n)
{
	len=0;
	memset(dp,0,sizeof(dp));
	int i,j,k,temp=n;
	i=0;
	while (temp>0)
	{
		num[++i]=temp%10;
		temp/=10;
	}
	len=i;
	for ( j = 0; j <num[len] ; j++)
	{
		dp[len][j][0]=1;
	}
	int ans=0;
	dp[len][j][1]=1;;
	for ( i = len-1; i >=1; i--)
	{
		for ( j = 0; j <= 9 ; j++)
		{		
			for ( k = 0; k <= 9 ; k++)
			{
				if ((len-i)%2==1 && j>=k)
				{
					dp[i][j][0]+=dp[i+1][k][0];
					if(j==num[i] && k==num[i+1])
						dp[i][j][1]+=dp[i+1][k][1];
					if (j<num[i])
						dp[i][j][0]+=dp[i+1][k][1];
					
				}
				else if((len-i)%2==0 && j<=k)
				{
					dp[i][j][0]+=dp[i+1][k][0];
					if(j==num[i] && k==num[i+1])
						dp[i][j][1]+=dp[i+1][k][1];
					if(j<num[i])
						dp[i][j][0]+=dp[i+1][k][1];
				}
			}
		}
	}
	for ( i = 0; i <= 9; i++)
	{
		ans+=(dp[1][i][0]+dp[1][i][1]);
	}
	return ans;
}
int main()
{
	int t,n,m,a,b;
	int ans;
	int flag;
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d%d",&n,&m);
		int temp=n,len;
		int i=0;
		flag=1;
	while (temp>0)
	{
		num[++i]=temp%10;
		temp/=10;
	}
	len=i;
	for ( i = len; i >=1 ; i--)
	{
		if((len-i)%2==0 && i<len && num[i] > num[i+1])flag=0;
		if((len-i)%2==1 && num[i] <num[i+1])flag=0;
	}
		printf("%d\n",find_dp(m)-find_dp(n)+flag);
	}
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值