DP·数位DP(2)

8 篇文章 0 订阅

题目:

CodeForces 55D

 

题意:

若一个数能被其各个位上的数整除,即满足条件。求区间[l, r]内所有满足条件的数

 

方法:

若想要一个数能被其各个数位上的数整除,则此数要能被各个位上的数的LCM整除。而且由于LCM(1~9) = 2520,所以将数对2520取模后,若余数能被LCM整除,则原数也能被LCM整除,因为LCM肯定为2520的一个因子。

(期初几个数位DP都是用循环写的,结果后来发现有dfs的更具普遍应用的写法,更容易理解且能被更广地使用,虽然速度肯定不如循环的写法……但胜在简单实用)

数位DP,通过[l, r] = [1,r]- [1,l-1]求得最终答案(其实此方法也可以看成一种记忆化搜索,只是通过数位DP的思想优化了状态,减少了大量的复杂度)

 

定义状态:dfs(int n, int lcm, int m, bool flag)表示当前为第n位,之前所有位的LCM为lcm,且对之前构造出来的数对2520取模后为m,flag表示之前枚举的数知否全部到达上限(对于4567,则4512的前两位到达上限),即当前枚举的数是否受限

初始状态:dfs(len,1, 0, true)。

状态转移:

1若n为0,则枚举完了整个数列,返回m% lcm == 0。

2若之前所有数都达到了上限,即flag为true,则此位置可枚举的数为0~num[n](表示原数的第n位)

3若之前的数中有一个没到上限,则此位置无论是什么数,都不可能比原数大(类似循环的数位DP那个思想),则此位置可枚举的数为0~9

优化:由于1~9的数组合成的LCM是离散的,所以通过离散化lcm可减少空间复杂度。通过一个f[n][lcm][m]的数组进行记忆存储,减少时间复杂的,此数组存的是当长度为n,LCM为lcm,余数为m时所有满足条件的数的个数,所以要直接返回f数组的值时,不仅需要f数组的值存在,还要flag为false,即此时不受限

PS:由于第一次f数组开成了f[n][m][lcm]……结果调试了半天……算是自己把自己又坑了一回


 

测试数据:

2

1 1000

1 10000

 

答案:

138

752

 

 

 

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<climits>
#include<cstdlib>
using namespace std;

//#define DEBUG
const int MAXMOD = 2520;
typedef __int64 ll;

ll f[20][50][MAXMOD+100];
int h[MAXMOD+100];
int num[20];

ll xGcd(ll a, ll b)
{
	return (a % b == 0)? b:xGcd(b, a % b);
}

ll xLcm(ll a, ll b)
{
	return a / xGcd(a, b) * b;
}

ll dfs(int n, int lcm, int m, bool flag)
{
#ifdef DEBUG
	cout<<"n="<<n<<"   flag"<<((flag)? 1:0)<<endl;
#endif
	if ( !n ) return m % lcm == 0;
	if ( ~f[n][h[lcm]][m] && !flag ) return f[n][h[lcm]][m];

	int k = (flag? num[n]:9);
#ifdef DEBUG
	cout<<"k="<<k<<endl;
#endif
	ll ans = 0;
	for (int i = 0; i <= k; i++){
		int cLcm = (i)? xLcm(lcm, i):lcm;
		int cM = (m * 10 + i) % MAXMOD;
		ans += dfs(n - 1, cLcm, cM, flag && (i == k));
	}
	if ( !flag ) f[n][h[lcm]][m] = ans;
	return ans;
}

ll getAns(ll a)
{
#ifdef DEBUG
	cout<<"num="<<a<<endl;
#endif
	int len = 0;
	while ( a ) num[++len] = a % 10, a /= 10;

#ifdef DEBUG
	for (int i = len; i; i--) cout<<num[i]<<"   ";cout<<endl;
	cout<<"dfs="<<dfs(len, 1, 0, true)<<endl;
#endif
	return dfs(len, 1, 0, true);
}

int main()
{
	//freopen("in.txt", "r", stdin);
	
	int cn = 0;
	for(int i = 1; i <= MAXMOD; i++) if ( MAXMOD % i == 0 ) h[i] = cn++;
	#ifdef DEBUG
	cout<<"cn="<<cn<<endl;
#endif
	memset(f, -1, sizeof(f));

	int T;
	scanf("%d", &T);
	while ( T-- )
	{
		ll l, r;
		scanf("%I64d%I64d", &l, &r);
		printf("%I64d\n", getAns(r) - getAns(l-1));
	}


    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值