【2019/03/30测试T3】里程表

【题目】

传送门

题目描述:

农民约翰的牛正开始一个美妙的旅程。牛车的里程表上显示一个整数表示里程,旅程开始时里程数为 x ( 100 ≤ x ≤ 1 0 18 ) x(100 \le x \le 10^{18}) x(100x1018),结束时里程数为 y ( x ≤ y ≤ 1 0 18 ) y(x \le y \le 10^{18}) y(xy1018)。每当里程表显示一个有趣的数时(包括起点和终点数),牛们会发出愉快的叫声。

对于一个里程数的每一位,如果有至少一半的数字时相同的,则这个里程数一个有趣的数。例如: 3223 3223 3223 110 110 110 是有趣的数,而 97791 97791 97791 123 123 123 则不是。

请计算,整个旅程中,牛们会发出多少吃愉快的叫声。

输入格式:

一行两个数,分别表示 x , y x,y x,y

输出格式:

一行一个数,表示答案。

样例数据:

输入
110 133

输出
14


【分析】

看到题目就知道这是一道数位 D P \mathrm{DP} DP的题。

那在 D P \mathrm{DP} DP 的时候,怎么来计算有没有某一个数字有没有超过一半呢?(这也是考场上一直困扰我的问题。)

我们可以枚举每个数( 0 0 0 ~ 9 9 9),就让这个数超过一半(将这个数设为 x x x)。

这样,问题就转化成了,计算 [    l , r    ] [\;l,r\;] [l,r] 中,各个位上 x x x 超过一半的数的个数,这就是一道简单题了。

然后把 0 0 0 ~ 9 9 9 中的答案累加一下就好了。

然后注意两点:

  1. 对于 1919 1919 1919 这样的数,枚举 1 1 1 的时候会算一次,枚举 9 9 9 的时候会再算一次,就会算重,要减去重复的。
  2. 注意要除去前导零的影响。

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int a[20],A,B;
ll f[20][20][20][2][2];
ll dp1(int p,int num,int all,bool lead,bool limit)
{
	if(!p)  return lead?0:(num*2>=all);
	if(~f[p][num][all][lead][limit])  return f[p][num][all][lead][limit];
	int i,up=limit?a[p]:9;ll ans=0;
	for(i=0;i<=up;++i)
	{
		if(lead&&i==0)  ans+=dp1(p-1,num,all-1,true,limit&&a[p]==i);
		else  ans+=dp1(p-1,num+(i==A),all,false,limit&&a[p]==i);
	}
	f[p][num][all][lead][limit]=ans;
	return ans;
}
ll g[20][20][20][20][2][2];
ll dp2(int p,int num1,int num2,int all,bool lead,bool limit)
{
	if(!p)
	{
		if(lead)  return 0;
		if(all&1)  return 0;
		if(num1*2==all&&num2*2==all)  return 1;
		return 0;
	}
	if(~g[p][num1][num2][all][lead][limit])  return g[p][num1][num2][all][lead][limit];
	int i,up=limit?a[p]:9;ll ans=0;
	for(i=0;i<=up;++i)
	{
		if(lead&&i==0)  ans+=dp2(p-1,num1,num2,all-1,true,limit&&a[p]==i);
		else  ans+=dp2(p-1,num1+(i==A),num2+(i==B),all,false,limit&&a[p]==i);
	}
	g[p][num1][num2][all][lead][limit]=ans;
	return ans;
}
ll solve(ll x)
{
	int i,j,p=0;ll ans=0;
	while(x)  a[++p]=x%10,x/=10;
	for(i=0;i<=9;++i)
	{
		memset(f,-1,sizeof(f));
		A=i,ans+=dp1(p,0,p,true,true);
	}
	for(i=0;i<=9;++i)
	{
		A=i;
		for(j=i+1;j<=9;++j)
		{
			memset(g,-1,sizeof(g));
			B=j,ans-=dp2(p,0,0,p,true,true);
		}
	}
	return ans;
}
int main()
{
	ll l,r;
	scanf("%lld%lld",&l,&r);
	printf("%lld",solve(r)-solve(l-1));
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值