当前位置: 代码迷 >> 综合 >> codeforces55D. Beautiful numbers(数位dp+数论)
  详细解决方案

codeforces55D. Beautiful numbers(数位dp+数论)

热度:69   发布时间:2023-12-06 20:00:24.0

传送门:http://codeforces.com/problemset/problem/55/D

题目大意:

求在[l,r]中能够整除自己每个数位上的数字的数的个数。


首先给出解这道题需要知道的一个知识:sum%(x*n) %x == sum%x。这个公式的原理其实想想就知道。

题目分析:

  • 首先我们能够知道如果这个数能够整除它的每个数位上的数字,那么它一定能够整除他们的最小公倍数,是充要的。
  • 那么我们定义状态dp[i][j][k]代表i位在任意组合下,得到的所有数位的数字的最小公倍数为j,且该数%2520为k的方案数。
  • 我们可以知道任意多个1-9之间的数的公倍数最大不会超过2520,而且他们都是2520的约数,所以(一个数%2520)能够被该数所有数位的数字的最小公倍数整除,那么该数就能整除自己每个数位上的数字
  • 所以我们利用这样状态记录进行记忆化搜索,对于给定的数字求取比它小的所有的符合要求的数,间接的求出区间内合法的数的个数,具体求法就是对于给定数的每一位,枚举它可能的数字,如果小于当前位i的数字,那么后面方案数就是dp[n-i][j][k],找出满足条件的j和k即可,利用深搜来实现。如果等于当前位,那么固定当前位,更新lcm,然后转移到下一位去寻找方案数。
  • 在逐位统计时,可以直接由前面位取余后的值来得到包含新一位的新数字取余后的值。
    例如 RX(R是已知前面位取余后的值),那么RX%2520 == (R*10+X)%2520。就不证明了。
  • dp数组理应是dp[20][2520][2520],lcm按2520取值根本开不下,所以对lcm进行离散化,因为lcm一定可以整除2520,所以将1~2520可以整除2520的数进行标记即可,测试后发现只有48个,满足当前情况。所以就成了dp[20][50][2550]
  • 清楚了状态就是一个简单的枚举和统计的操作了。具体看代码吧….写的不清楚的地方欢迎评论指正。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 2520;
LL dp[20][50][2550];//dp[i][j][k],i表示位数,j表示所有数位的最小公倍数,k表示对2520的余数
int dis[20];//用来保存原数的每一位数字,低位的数保存的下标为0,最高位为len-1
int Hash[2550];//
LL dfs(int len, int lcm, int have, bool lim)//len表示位数/lcm为当前每位数字的lcm/have为当前这个数对2520的余数/lim为是否达到上限
{if(len == -1)return have % lcm == 0;if(!lim && dp[len][Hash[lcm]][have] != -1)return dp[len][Hash[lcm]][have];LL ans = 0;int num = lim ? dis[len] : 9;//如果前面的位都没有达到上限,下一位就可以为0~9,否则只能为0~dis[len]for(int i = 0; i <= num; i++)//枚举所有允许的下一位,所有达到上限的状态,都由递归的下一层结束后返回ans += dfs(len-1, i ? lcm*i/__gcd(lcm, i):lcm, (have*10+i)%MOD, lim&&i==num);if(!lim)dp[len][Hash[lcm]][have] = ans;return ans;
}
LL solve(LL n)
{int pos = 0;while(n){dis[pos++] = n % 10;n /= 10;}return dfs(pos-1, 1, 0, true);
}
int main()
{int T;scanf("%d", &T);int cnt = 0;for(int i = 1; i<= MOD; i++)if(MOD % i == 0)Hash[i] = cnt++;memset(dp, -1, sizeof(dp));while(T--){LL l, r;scanf("%I64d%I64d", &l, &r);printf("%I64d\n", solve(r) - solve(l-1));}return 0;
}