bzoj2757: [SCOI2012]Blinker的仰慕者

bzoj2757: [SCOI2012]Blinker的仰慕者

Description

Blinker 有非常多的仰慕者,他给每个仰慕者一个正整数编号。而且这些编号还隐藏着特殊的意义,即编号的各位数字之积表示这名仰慕者对Blinker的重要度。 现在Blinker想知道编号介于某两个值A,B之间,且重要度为某个定值K的仰慕者编号和。

Input

输入的第一行是一个整数N,表示Blinker想知道的信息个数。
接下来的N行,每行有三个数,A,B,K。表示 Blinker想知道编号介于A和B之间的,
重要度为K的仰慕者的编号和。

Output

输出N行,每行输出介于A和B之间,重要度为 K的仰慕者编号和。结果可能很大,
模上20120427。

Sample Input

3
1 14 4
1 30 4
10 60 5
0<=K<=10^18

Sample Output

18
40
66
【样例解释】
第一组样例中,在 1到14之间各位数字之积等于 4的有 4和 14,故编号和为18。

HINT

【数据范围】
对于20%的数据,保证: 2<=A<=B<=1000000000,1<=N<=30
对于50%的数据,保证:2<=A<=B<=1000000000000000000,1<=N<=30
对于100%的数据,保证: 2<=A<=B<=1000000000000000000,1<=N<=5000

分析

一道很好的数位Dp
可以预计到,乘积的组合应该不多,经计算只有4.4e4种
所以如果是问个数的话就是一个很裸的数位Dp,开个f[i][j]表示第i位,乘积为j的个数。
但是如果是问和的话就要多搞一下,开个g[i][j]表示第i位,乘积为j的和。
前两个东西很好Dp出来,具体看代码。
然后最后统计答案要一些奇技淫巧。
如果确定了一个数的前缀Pr,那么生成的方案数是
Prf[i][K]10i+g[i][K] P r ∗ f [ i ] [ K ] ∗ 10 i + g [ i ] [ K ]
然后Dp要有一个小技巧,前面的f,g递推一下,统计答案用搜索即可。
因为多组询问嘛。
复杂度很妥。

代码

#include<cstdio>
#include<map>
typedef long long LL;
std::map<LL, int>m;
const int N = 5e4, P = 20120427;
int pw[21], bit[21], g[21][N], f[21][N], af[21], ag[21], tp, L;
long long K, st[N];
void Up(int &a, int b) {a += b; if(a >= P) a -= P;}
void Pre() {
    pw[0] = 1; for(int i = 1;i <= 20; ++i) pw[i] = pw[i - 1] * 10 % P;
    for(int i = 0;i <= 9; ++i) {
        m[i] = ++tp; st[++L] = i;
        f[1][tp] = 1; g[1][tp] = i;
    }
    for(int i = 1, nL = L;i <= 18; ++i, L = nL)
        for(int j = 1;j <= L; ++j) {
            LL x = st[j]; int pr = m[x];
            for(int k = 0;k <= 9; ++k) {
                if(!m[x * k]) st[++nL] = x * k, m[x * k] = ++tp; 
                int nx = m[x * k]; Up(f[i + 1][nx], f[i][pr]);
                Up(g[i + 1][nx], (1LL * f[i][pr] * pw[i] * k + g[i][pr]) % P);
            }
        }
    for(int i = 1;i < 20; ++i)
        for(int j = 1;j <= L; ++j)
            Up(af[i], f[i][j]), Up(ag[i], g[i][j]);
}
int Dfs0(int i, int s, bool Z, bool e, bool z) {
    if(!i) return Z ? s : 0;
    if(!e && !z) {
        int A = !Z ? f[i][1] : af[i], B = !Z ? g[i][1] : ag[i];
        return (1LL * s * A % P * pw[i] + B) % P;
    }
    int end = e ? bit[i] : 9, r = 0;
    for(int v = 0;v <= end; ++v) 
        Up(r, Dfs0(i - 1, (s * 10 + v) % P, Z | !v & !z, e && (v == end), z && !v));
    return r;
}
int Dfs(int i, int s, LL K, bool e, bool z) {
    if(!i) return K == 1 ? s : 0;
    int t = m[K]; if(!t) return 0;
    if(!e && !z) return (1LL * s * f[i][t] % P * pw[i] + g[i][t]) % P;
    int end = e ? bit[i] : 9, r = 0;
    if(z) Up(r, Dfs(i - 1, s, K, e && !end, 1));
    for(int v = 1;v <= end; ++v) 
        if(!(K % v)) Up(r, Dfs(i - 1, (s * 10 + v) % P, K / v, e && (v == end), 0));
    return r;
}
int Cal(long long x) {
    int tp = 0; for(;x; x /= 10) bit[++tp] = x % 10;
    if(K != 0) return Dfs(tp, 0, K, 1, 1);
    else return Dfs0(tp, 0, 0, 1, 1);
}
int main() {
    Pre();
    int T; for(scanf("%d", &T); T--; ) {
        LL A, B; scanf("%lld%lld%lld", &A, &B, &K);
        printf("%d\n", (Cal(B) - Cal(A - 1) + P) % P);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值