数位dp专题之Beautiful numbers

持续更新中……

Problem - D - Codeforces

Beautiful numbers

数位dp之dp手的折戟

我真的好讨厌写dfs啊

dp?dfs!

幸好还是做出了一道,不然就真罚座一晚上了。

给t组查询,求l到r之间有多少个数字,满足其每一位数都是其因子

首先找到1-9的最小公倍数为2520,它的48个因子分别对应的是1-9不同组合下的最小公倍数。通过离散化把48个因子标序1-48

再者,因为需要进行状态的转移,在记录数字铁定会爆的情况下我们选择记录对2520取模后的结果,通过对余数的处理进行转移。

看这数字肯定不能暴力捏,但是结果和位数存在关系,考虑枚举位数,并且记录此时是否达到上限。

而余数*10+位数可能性为新的余数,由此进行动态转移

设dp[j][i][k],当前位为j,余数为i,最小公倍数ak下的ans

dfs(pos,presum,prelcm,flag) 分别为 当前位数,当前余数,当前lcm,是否达到上限       

其余的看看代码吧

//给n个范围,求区间内符合条件的元素个数
//如果是2520的倍数一符合条件 如果不是,看余数 
//dp[j][i][k] 当前位为j,余数为i,最小公倍数ak下的ans


#include<bits/stdc++.h> 
using namespace std;

const long long mod=2520;
long long a[mod];
long long num1[25];
long long dp[10][2520][48];
void _init_(){//预处理出所有因数和id的关系
	long long cnt=0;
	for(long long i=1;i<=mod;i++)
	{
		if(mod%i==0) a[i]=cnt++;
	}
}
long long lcm(long long a,long long b)//求lcm
{
	return a/__gcd(a,b)*b;
}
long long dfs(long long pos,long long presum,long long prelcm,bool flag)//从高位向低位枚举
{
	if(pos==-1) return presum%prelcm==0;//所有位数都枚举过了
	if(!flag&&dp[pos][presum][a[prelcm]]!=-1)//已达上限且已经算过了
		return dp[pos][presum][a[prelcm]];
	long long ans=0;
	long long end = flag ? num1[pos] : 9;//没到上限就取上限,到了就取9
    for (long long i = 0; i <= end; i++) {
        long long nowSum = (presum * 10 + i) % mod;//处理余数
        long long nowLcm = prelcm;
        if (i) {
            nowLcm = lcm(nowLcm, i);//更新lcm
        }
        ans += dfs(pos - 1, nowSum, nowLcm, flag && i == end);
    }
    if (!flag) {//已达上限但是没算过
        dp[pos][presum][a[prelcm]] = ans;
    }
    return ans;
}
long long calc(long long x)
{
	long long cnt=0,n=x;
	while(n>0)
	{
		num1[cnt++]=n%10; 
		n/=10;
	}
	return dfs(cnt-1,0,1,1);
}
int main()
{
	_init_();
	  memset(dp, -1, sizeof(dp)); 	
	long long _,l,r;
	cin>>_;
	while(_--)
	{
		
		cin>>l>>r;
		cout<<calc(r)-calc(l-1)<<"\n";
	}
}

大致就这样吧我困了我真的不会我要去吃夜宵了qaq

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值