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

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

#include 
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;
}

你可能感兴趣的:(动态规划-数位DP)