题面

我们定义一个数是单调数,当且仅当构成这个数每一个数位都是单调不降或不增的。

例如 \(123\) 和 \(321\) 和 \(221\) 和 \(111\) 是单调的,而 \(312\) 不是单调的。

给定 \(T\) 组 \(l, r\),每次询问 \([l, r]\) 中有几个单调的数。

\(l, r \le 10 ^ {18}, T \le 10^4\)

题解

今天 hihoCoder 竟然 \(AK\) 了,真舒服(虽然题目很水,还被罚时坑惨了)qwq

显然考虑一个数位 \(dp\) ,不难发现我们只需要记下当前在哪一位,以及最后一位是什么数字就行了。

然后对于不降和不增做个两遍就行了,但有很多细节后面细讲。

写的时候发现自己摸索了一套数位 \(dp\) 的套路?(逃

套路:

我们常常是要求 \(\le n\) 的有多少个满足要求的数。

这个限制有些恶心,我们需要多一位来看是否被限制。

我们一般按位考虑,令 \(dp[i][0 / 1]\) 为到从高到低考虑到第 \(i\) 位,当前有/没被 \(n\) 限制。

我们考虑把 \(n\) 按位拆下来,变成一个数组 lim[i] ,然后取出 \(n\) 的位数 Bit

每次考虑后一位放什么数字就行了。具体实现如下(用刷表法方便转移):

dp[0][0] = 1;
for (int i = 0; i < Bit; ++ i)
for (int now = 0; now <= 1; ++ now)
for (int dig = 0; dig <= 9; ++ dig) if (now || (dig <= lim[i + 1]))
dp[i + 1][now || (dig < lim[i + 1])] += dp[i][now];

然后最后的答案就是

ans = dp[Bit][0] + dp[Bit][1];

不难发现这样写,每个位数的数都会被考虑到。因为我们枚举的时候允许了前缀 \(0\) 的存在。

并且如果存在前缀 \(0\) 那么后面的所有数都不会存在限制了,可以随便填。

但是注意这样的话,全部为 \(0\) 的也会考虑进去,我们平常要考虑是否 \(-1\) 就行了。

对于这道题我们可以类比这种方法去做。

首先把答案差分表示 \(ans = ans_r - ans_{l - 1}\) 。

然后令 dp[i][j][0/1] 为到第 \(i\) 位,最后一次填 \(j\) ,有/没 被 \(n\) 限制住的情况。

直接这样写的话,递增是没有问题的,递减的时候就会存在问题了。

因为我们把前导 \(0\) 考虑进去了,结果导致没有正确算上这部分贡献。

所以我们还要多一维,也就是 dp[i][j][0/1][0/1] 前三个同上,最后一个意义是当前完全不/是前缀 \(0\) 。

然后转移的时候也是枚举数字,然后按照两种情况考虑下填的这个数的限制就行了。

注意有一些数会被递增递减算两次,也就是 \(111\) ,\(3333\) ,这些完全相同的数。可以直接暴力枚举减去就行了。

复杂度是 \(O(T * 18 * 10)\) 的。

总结

碰到数位 \(dp\) 直接上套路去讨论。

然后就需要对于具体问题具体分析了,根据题目要求设出需要的状态。

有时候可以根据需要卡卡状态数,因为通常不可能到满。

一定要写个暴力拍,这个东西其实很好调?

代码

建议看看代码,其实写的很简洁?(可读性也是很鲁棒的qwq)

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * fh;
} void File() {
#ifdef zjp_shadow
freopen ("1770.in", "r", stdin);
freopen ("1770.out", "w", stdout);
#endif
} typedef long long ll;
ll dp[19][10][2][2]; int lim[19];
inline int Get_Bit(ll x) {
int tot = 0;
for (; x; x /= 10) lim[++ tot] = x % 10;
reverse(lim + 1, lim + tot + 1);
return tot;
} inline ll Calc(ll n) { if (!n) return 0; ll ans = 0; For (opt, 0, 1) {
Set(dp, 0); dp[0][0][0][1] = 1; int Bit = Get_Bit(n);
For (i, 0, Bit - 1) For (j, 0, 9) For(now, 0, 1) For (fir, 0, 1) {
For (dig, opt ? 0 : j, opt ? (fir ? 9 : j) : 9) if (now || dig <= lim[i + 1])
dp[i + 1][dig][now || (dig < lim[i + 1])][fir && !dig] += dp[i][j][now][fir];
} For (j, 0, 9) For(now, 0, 1) For (fir, 0, 1) ans += dp[Bit][j][now][fir]; -- ans;
} For (dig, 1, 9) {
ll tmp = dig;
while (tmp <= n) -- ans, tmp = tmp * 10 + dig;
} return ans; } int main() { File(); for (int cases = read(); cases; -- cases) {
ll l, r; cin >> l >> r;
printf ("%lld\n", Calc(r) - Calc(l - 1));
} return 0; }

hihoCoder #1770 : 单调数(数位dp)的更多相关文章

  1. 【BZOJ1662】[Usaco2006 Nov]Round Numbers 圆环数 数位DP

    [BZOJ1662][Usaco2006 Nov]Round Numbers 圆环数 Description 正如你所知,奶牛们没有手指以至于不能玩"石头剪刀布"来任意地决定例如谁 ...

  2. 【BZOJ-1026】windy数 数位DP

    1026: [SCOI2009]windy数 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 5230  Solved: 2353[Submit][Sta ...

  3. CCF 201312-4 有趣的数 (数位DP, 状压DP, 组合数学+暴力枚举, 推公式, 矩阵快速幂)

    问题描述 我们把一个数称为有趣的,当且仅当: 1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次. 2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前. 3. 最高 ...

  4. bzoj 1026 [SCOI2009]windy数 数位dp

    1026: [SCOI2009]windy数 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline ...

  5. luogu P2657 [SCOI2009]windy数 数位dp 记忆化搜索

    题目链接 luogu P2657 [SCOI2009]windy数 题解 我有了一种所有数位dp都能用记忆话搜索水的错觉 代码 #include<cstdio> #include<a ...

  6. 【BZOJ 3326】[Scoi2013]数数 数位dp+矩阵乘法优化

    挺好的数位dp……先说一下我个人的做法:经过观察,发现这题按照以往的思路从后往前递增,不怎么好推,然后我就大胆猜想,从前往后推,发现很好推啊,维护四个变量,从开始位置到现在有了i个数 f[i]:所有数 ...

  7. 洛谷P2657 [SCOI2009]windy数 [数位DP,记忆化搜索]

    题目传送门 windy数 题目描述 windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道, 在A和B之间,包括A和B,总共有多少个win ...

  8. 【bzoj1026】[SCOI2009]windy数 数位dp

    题目描述 windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个windy数? 输入 包含两个整数 ...

  9. [bzoj1026][SCOI2009]windy数——数位dp

    题目 求[a,b]中的windy数个数. windy数指的是任意相邻两个数位上的数至少相差2的数,比如135是,134不是. 题解 感觉这个题比刚才做的那个简单多了...这个才真的应该是数位dp入门题 ...

随机推荐

  1. jquery获取内容和属性的方法

    通过jquery如何捕获文本内容和属性? text(),html(),val()及attr(). attr()更具有普遍性,元素text属性和表单value属性,可以通过attr()操作. <! ...

  2. Android 网络框架 Retrofit2

    概述 Retrofit是一个OkHttp网络请求框架的封装库,Retrofit通过注解配置网络参数,可以按照我们的规则去构造实际的HTTP请求,能够灵活设置URL.头部.请求体.返回值等,是目前最优雅 ...

  3. Xamarin 打包生成 Android apk 文件

    Visual Studio 支持 apk 发布 Xamarin.Forms项目或Xamarin.Android项目开发完成之后需要发布.比较常规的发布方式是生成 apk 文件,微软也考虑到开发者有发布 ...

  4. vs2015启动崩溃,wpfgfx_v0400.dll加载D3DCompiler_47.dll失败

    有一段时间没有用vs2015,今天一启动就crash,尝试了以下办法: 1. 卸载掉这段时间安装的一些软件和vs插件 2. 修复安装 3. 卸载.重新安装vs2015 sp2版本 4. devenv. ...

  5. CentOS7.2重置root密码的处理方法

    第一个里程碑 --在启动GRUB菜单中选择编辑选项,按键 "e" 进入编辑; 第二个里程碑 -- 大约在第16行找到 "ro" 将 "ro" ...

  6. Redis学习笔记(2)——Redis的下载安装部署

    一.下载Redis Redis的官网下载页上有各种各样的版本,如图 但是官网下载的Redis项目不正式支持Windows.如果需要再windows系统上部署,要去GitHub上下载.我下载的是Redi ...

  7. 数据库【mysql】之pymysql

    安装模块 pip install pymysql 导入模块 import pymysql 创建链接 conn = pymysql.connect(host=') 创建索引 cursor = conn. ...

  8. 一位月薪1.2w的北漂程序员真实生活!

    “ 每个人都有一条生活道路.千万人,千万条,各不相同,各有特点.但是并不是好人都有一条好路,坏人都有一坏路.有的时候却恰恰相反.这虽然不是历史的必然,但却是客观社会存在的.今天咱们说的这部书是发生在当 ...

  9. Java HttpURLConnection发送post请求示例

    public static Map<String, Object> invokeCapp(String urlStr, Map<String, Object> params) ...

  10. 【原创】JAVA面试解析(有赞一面)

    本文的题目出自博客 http://www.54tianzhisheng.cn/2018/07/12/youzan/ 但是作者没有给出答案,博主斗胆来制作答案版. 引言 说在前面的话: 本文适合人群:急 ...