zoukankan      html  css  js  c++  java
  • 【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 imes 2520 imes 2520 imes log 2520(求LCM)×2×10 imes 2 imes 10(枚举转移的位)×10 imes 10(数据组数),T到没边,因此我们还要进一步进行优化。
    打表或手算发现,不同的jj的数目,也就是25202520的因数,只有4848个,因此用一个数组映射一下这些数,jj用映射后的值表示即可,优化掉一个5050。进而我们发现我们可以预处理出这些数之间的LCM,所以又优化掉了一个log2520log 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
    ",ans);
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    RabbitMQ指南之一:"Hello World!"
    Java8新特性之五:Optional
    Java8新特性之四:接口默认方法和静态方法
    Java8新特性之三:Stream API
    Java8新特性之二:方法引用
    Notepad++编辑器——Verilog代码片段和语法检查
    数电(5):半导体存储电路
    数电(4):组合逻辑电路
    DDR3_新版(1):IP核调取和官方例程仿真
    数电(2):逻辑代数的基本定理
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793244.html
Copyright © 2011-2022 走看看