【CF55D】Beautiful Numbers-数位DP+优化

测试地址:Beautiful Numbers
题目大意: 求在区间[L,R][L,R]中,有多少能整除自身所有非零数位的数。
做法: 本题需要用到数位DP+优化。
首先这题一看就是数位DP,本题的关键是状态的设计以及优化。
我们很快能写出一个状态定义:f(i,j,k,0/1)f(i,j,k,0/1)表示前ii位,所有非零位的LCM是jj,对jj的余数是kk,不卡/卡上界的数的数目。但我们发现这个东西不能转移,当jj增加的时候,新的余数会有很多种情况,因此我们不能考虑这种状态定义。
我们发现,涉及到的所有的jj都是LCM(1,2,...,9)=2520LCM(1,2,...,9)=2520的因数,所以我们只需要kk的表示改成对25202520的余数,那么kkjj的整数倍时,就表示这些数满足题目中的条件。
这样我们就能转移了。但分析一下发现,时间复杂度是1919(位数)×2520×2520×log2520\times 2520\times 2520\times \log 2520(求LCM)×2×10\times 2\times 10(枚举转移的位)×10\times 10(数据组数),T到没边,因此我们还要进一步进行优化。
打表或手算发现,不同的jj的数目,也就是25202520的因数,只有4848个,因此用一个数组映射一下这些数,jj用映射后的值表示即可,优化掉一个5050。进而我们发现我们可以预处理出这些数之间的LCM,所以又优化掉了一个log2520\log 2520。进行这两个优化后,已经非常接近时限了,但还是会T掉。进一步观察发现,卡上界的情况中只有一个数,这个数的j,kj,k是确定的,不用跟随上面的枚举,因此我们根本不用存0/10/1一维,只要维护当前上界的j,kj,k即可完成应完成的转移。所以又优化掉了一个22,就可以通过此题了,最大点的时间为3s3s左右。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int LCM=2520;
int T,n,s[20],lcmlist[1050],id[3010],L[50][10],tot;
ll f[21][LCM+10][50];

int gcd(int a,int b)
{
	return (b==0)?a:gcd(b,a%b);
}

int lcm(int a,int b)
{
	return a*b/gcd(a,b);
}

ll solve()
{
	memset(f[n+1],0,sizeof(f[n+1]));
	int toplcm=1,toprem=0;
	
	for(int i=n;i>=1;i--)
	{
		memset(f[i],0,sizeof(f[i]));
		for(int j=0;j<LCM;j++)
			for(int k=1;k<=tot;k++)
				for(int now=0;now<=9;now++)
					f[i][(j*10+now)%LCM][L[k][now]]+=f[i+1][j][k];
		if (i<n)
		{
			for(int now=0;now<s[i];now++)
				f[i][(toprem*10+now)%LCM][L[toplcm][now]]++;
		}
		for(int j=1;j<=((i==n)?(s[i]-1):9);j++)
			f[i][j][id[j]]++;
		toplcm=L[toplcm][s[i]];
		toprem=(toprem*10+s[i])%LCM;
	}
	ll ans=0;
	for(int i=1;i<=tot;i++)
		for(int j=0;j*lcmlist[i]<LCM;j++)
			ans+=f[1][j*lcmlist[i]][i];
	if (toprem%lcmlist[toplcm]==0) ans++;
	return ans;
}

int main()
{
	scanf("%d",&T);
	
	tot=0;
	for(int i=1;i<(1<<9);i++)
	{
		int x=1;
		for(int j=1;j<=9;j++)
			if ((1<<(j-1))&i) x=lcm(x,j);
		lcmlist[++tot]=x;
	}
	sort(lcmlist+1,lcmlist+tot+1);
	tot=0;
	for(int i=1;i<(1<<9);i++)
		if (i==1||lcmlist[i]!=lcmlist[tot])
		{
			lcmlist[++tot]=lcmlist[i];
			id[lcmlist[tot]]=tot;
		}
	for(int i=1;i<=tot;i++)
		for(int j=0;j<=9;j++)
			L[i][j]=id[lcm(lcmlist[i],max(j,1))];
	
	while(T--)
	{
		ll x;
		scanf("%I64d",&x);
		x--;
		
		ll ans=0;
		if (x)
		{
			n=0;
			while(x)
			{
				s[++n]=x%10;
				x/=10;
			}
			ans-=solve();
		}
		
		scanf("%I64d",&x);
		n=0;
		while(x)
		{
			s[++n]=x%10;
			x/=10;
		}
		ans+=solve();
		printf("%I64d\n",ans);
	}
	
	return 0;
}
posted @ 2018-10-10 21:43  Maxwei_wzj  阅读(142)  评论(0编辑  收藏  举报