[COCI2006-2007#6] V

前言

赛时联想到了讲的一道题认为不可以使用数位 dp , 但是那道题实际上形式上跟这个题不同, 所以其实是可以用的

思路

首先我们用数位 dp 可以简单地解决选择数字的问题, 套路的用 f(1,r)f(1,l1) 可以解决统计答案的问题, 还需要具体的讨论转移怎么做

考虑倍数的本质, 我们考虑套路的转移模 X 的结果, 但是因为 X1011 , 不可能直接转移

然后你发现, 我们有简单的 O(ABX) 算法可以枚举得到答案, 这样子我们可以简单地根号分治一下即可, 阈值什么的一会再说

同常规的数位 dp 一样, 这个题一定也需要设计状态为 dppos,m,lim,lead 记录到达位置, 当前的模数, 是否有限制, 是否有前导 0 时的方案数

稍微特殊的是如果当前还在前导零状态下, 无论 S 的状态如何都可以继续往里面加上 0 , 这也是符合条件的方案数

总时间复杂度 O(ABsz+szω) , 其中 ω11×10=110 , 阈值取到 sz=104105 就可以了

实现

框架#

数据点分治,

  • xsz , 使用数位 dp
  • x>sz , 使用暴力方法

数位 dp 还是很传统的

代码#

#include <bits/stdc++.h>
#define int long long
const int SZ = 10000;
const int MAXSZ = 15;
const int MAXVAL = 10000; // 记得改回来

int X, A, B;
bool S[10];

class BruteForces
{
private:
    int st;
    int ans = 0;
    bool check(int x) {
        while (x) { if (!S[x % 10]) return false; x /= 10; }
        return true;
    }

public:
    void solve() {
        st = (A % X) ? A / X + 1 : A / X; st *= X;
        for (int i = st; i <= B; i += X) if (check(i)) ans++;
        printf("%lld", ans);
    }
} BF;

class Intelligence
{
private:
    int num[MAXSZ];
    int dp[MAXSZ][MAXVAL][2][2];

    /*从高位往低位推的数位 dp 的记忆化搜索*/
    int dfs(int pos, int m, bool lead, bool limit) {
        int ans = 0;
        if (!pos) return (!lead && !m); // 边界条件
        if (~dp[pos][m][lead][limit]) return dp[pos][m][lead][limit];

        int up = limit ? num[pos] : 9;
         for (int i = 0; i <= up; i++) {
            if (i == 0 && lead) { ans += dfs(pos - 1, (m * 10 + i) % X, true, (i == up) & limit); continue; } // 特殊的情况
            if (!S[i]) continue; // 不使用违法数字
            ans += dfs(pos - 1, (m * 10 + i) % X, false, (i == up) & limit);
        }
        return dp[pos][m][lead][limit] = ans;
    }

    int f(int x) {
        int len = 0; // 递推数字串的长度
        while (x) { num[++len] = x % 10; x /= 10; }
        memset(dp, -1, sizeof(dp)); // 表示没有处理过, 而非初值(这点跟树形 dp 不同)

        return dfs(len, 0, true, true); // 传统
    }

public:
    void solve() {
        printf("%lld", f(B) - f(A - 1));
    }
} It;

signed main()
{
    scanf("%lld %lld %lld", &X, &A, &B);
    std::string a; std::cin >> a;
    for (int i = 0; i < a.length(); i++) S[a[i] - '0'] = true;

    if (X > SZ) BF.solve();
    else It.solve();

    return 0;
}

总结

根号分治思想非常好玩

枚举倍数的常见转移

posted @   Yorg  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示