数位dp小练

最近刷题的同时还得填填坑,说来你们也不信,我还不会数位dp。

照例推几篇博客:

数位DP讲解

数位dp 的简单入门

这两篇博客讲的都很好,不过代码推荐记搜的形式,不仅易于理解,还短。

数位dp的式子一般是这样的:dp[i][][]表示到第\(i\)位,而后面几维就因题而异了。

不过通用的思想就是利用前缀相减求出区间信息。

算了上题吧。

[SCOI2009]windy数

这都说是数位dp入门题。

根据这题,受到影响的数只有相邻两个,因此dp[i][j]表示到第\(i\)位(从高往低)上一位的数\(j\)的答案。

接下来的关键在于怎么判断到达上界的情况。那么我们在搜索的时候加一个bool变量_Max表示是否到达上界,如果是,那么这一位的最大值就是该位上的数,否则就是9。

然后关于前导零,我又开了一个bool变量zero判断他之前有没有过非0的数。

接下来就是记搜的内容了。先写一个爆搜,然后把答案存在dp里,基本就是记搜了。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("")
#define space putchar(‘ ‘)
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 12;
inline ll read()
{
    ll ans = 0;
    char ch = getchar(), last = ‘ ‘;
    while(!isdigit(ch)) last = ch, ch = getchar();
    while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - ‘0‘, ch = getchar();
    if(last == ‘-‘) ans = -ans;
    return ans;
}
inline void write(ll x)
{
    if(x < 0) x = -x, putchar(‘-‘);
    if(x >= 10) write(x / 10);
    putchar(x % 10 + ‘0‘);
}

int dp[maxn][maxn], num[maxn], cnt = 0;

In int dfs(int len, int las, bool _Max, bool zero)
{
    if(!len) return 1;  //表示出现了一个符合条件的数,就返回1
    if(!_Max && !zero && dp[len][las] != -1) return dp[len][las];
    //只有普通情况才可以返回记搜的答案.
    int pos, ret = 0, Max = _Max ? num[len] : 9;
    for(int i = 0; i <= Max; ++i)
    {
        if(abs(i - las) < 2) continue;
        pos = (zero && !i) ? -INF : i;  //如果前面都是0且这一位还填0,就标记为INF
        ret += dfs(len - 1, pos, _Max && i == Max, pos == -INF);
    }
    if(!_Max && !zero) dp[len][las] = ret;
    return ret;
}

In int solve(int n)
{
    Mem(dp, -1); cnt = 0;
    while(n) num[++cnt] = n % 10, n /= 10;  //处理每一位
    return dfs(cnt, -INF, 1, 1);
}

int main()
{
    int a = read(), b = read();
    write(solve(b) - solve(a - 1)), enter;
    return 0;
}

luogu P3413 SAC#1 - 萌数

这题反正我是不会,看了题解也没懂。最后在dukelv的帮助下搞明白了。

首先题解中有很多篇用了正难则反的思想,但其实没必要。

考虑最小的回文串,无非两种:aa, aba。

所以我们只用找出这两种回文串即可。因为大的回文串比如acbbca,在搜bb的时候,下几个状态就包括这个回文串了。

那么就要讨论奇偶,所以爆搜的时候开两个变量pre,per分别记录上一位和上上一位,然后判断和当前位是否构成回文串。

别忘了还可能会出现构不成萌数的情况,所以dp方程除了dp[i][j]表示到了第\(i\)位,这一位填\(i\),还应再加一位[0/1]表示当前是否存在萌数,这样能剪枝不少。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("")
#define space putchar(‘ ‘)
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e3 + 5;
const ll mod = 1e9 + 7;
inline ll read()
{
    ll ans = 0;
    char ch = getchar(), last = ‘ ‘;
    while(!isdigit(ch)) last = ch, ch = getchar();
    while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - ‘0‘, ch = getchar();
    if(last == ‘-‘) ans = -ans;
    return ans;
}
inline void write(ll x)
{
    if(x < 0) x = -x, putchar(‘-‘);
    if(x >= 10) write(x / 10);
    putchar(x % 10 + ‘0‘);
}

char a[maxn], b[maxn];
int num[maxn], len = 0;

ll dp[maxn][10][2];
In ll dfs(int pos, int pre, int per, bool _t, bool _k, bool _Max)
{
  if(!pos) return _t;  //_t表示当前是否存在萌数
    if(!_Max && dp[pos][pre][_t] != -1) return dp[pos][pre][_t];
    int Max = _Max ? num[pos] : 9;
    ll ret = 0;
    for(int i = 0; i <= Max; ++i)
        ret += dfs(pos - 1, i, _k ? pre : -1, _t || (i == pre && _k) || (i == per && _k), _k || i, _Max && i == Max), ret %= mod;
    if(!_Max && _k && per != -1) dp[pos][pre][_t] = ret % mod;
    return ret; 

}
In ll solve(char* s)
{
    len = strlen(s + 1);
    for(int i = 1; i <= len; ++i) num[i] = s[i] - ‘0‘;
    reverse(num + 1, num + len + 1);
    Mem(dp, -1);
    return dfs(len, -1, -1, 0, 0, 1);
}

int main()
{
    scanf("%s%s", a + 1, b + 1);
    int tp = strlen(a + 1);
    reverse(a + 1, a + tp + 1);
    int pos = 1;  //别忘了是高精减1……
    --a[pos];
    while(pos < tp && a[pos] < ‘0‘) a[pos++] = ‘9‘, --a[pos];
    while(tp > 1 && a[tp] <= ‘0‘) a[tp--] = ‘\0‘;
    reverse(a + 1, a + tp + 1);
    write((solve(b) - solve(a) + mod) % mod), enter;
    return 0;
}

原文地址:https://www.cnblogs.com/mrclr/p/10367978.html

时间: 2024-08-24 23:31:19

数位dp小练的相关文章

hdu4734 数位dp + 小技巧

hdu-4734 题意:假设x的10进制数每一位分别为(AnAn-1An-2 ... A2A1),定义  F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,求1-b中F(x)<=F(a)的数有多少个 思路:其实F(x)只是给每一个数位带上一个权值v=2^(p-1),F(x)最大是值不会超过5000,我们完全可以抛开权值来思考,写代码的时候再加上权值即可,这样思考和写草稿之类的会方便很多,不考虑每一位的权值的话即是数位的前缀和, 很容易想

数位DP小小结

FZOJ Problem 2113Jason的特殊爱好 题意:x~y数字里面有多少个 1 思路:我们算法课实验题的简化版,当时我用了很麻烦的一个DP=_= 刚刚学到了很棒的姿势,记忆化DP!! dfs(int pos ,bool end1) ; end1==false 返回pos位后面(包含pos)任意组合有多少个 1 : end1==true 返回上一位是结尾,Pos以后的位受到限制组合有多少个 1 : 大概是这样,如果数字是 4987 现在计算到 8 这个数字,end1==true,说明是4

【树形dp小练】HDU1520 HDU2196 HDU1561 HDU3534

[树形dp]就是在树上做的一些dp之类的递推,因为一般需要递归处理,因此平凡情况的处理可能需要理清思路.昨晚开始切了4题,作为入门训练.题目都非常简单,但是似乎做起来都还口以- hdu1520 Anniversary party 给一颗关系树,各点有权值,选一些点出来.任一对直接相连的边连接的点不能同时选择,问选择出的权值和最大为多少. 考虑以u为根的子树可以选择的方法,dp[u]表示选择u时能获得最大收益,dp2[u]表示不选u时能获得的最大收益.则u不选时,为dp2[u]=max{dp[v]

hdu 3709 数位dp(小思维)

http://acm.hdu.edu.cn/showproblem.php?pid=3709 Problem Description A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit.

SCUT - 289 - 小O的数字 - 数位dp

https://scut.online/p/289 一个水到飞起的模板数位dp. #include<bits/stdc++.h> using namespace std; typedef long long ll; bool notp[2000]; const int MAXS1=200; const int MAXS2=2000; int a[20]; ll dp[20][MAXS1][MAXS2]; ll dfs(int pos,int s1,int s2,bool lead,bool l

[CSP-S模拟测试]:小L的数(数位DP+模拟)

题目传送门(内部题132) 输入格式 第一行一个整数$t$. 接下来$t$行每行一个整数$n$. 输出格式 $t$行,每行一个整数表示答案. 样例 样例输入: 418182312326910521093203 样例输出: 1233 数据范围与提示 对于前$5\%$的测试数据,满足答案小于等于$1$. 对于前$20\%$的测试数据,满足答案小于等于$2$. 对于前$40\%$的测试数据,满足$n\leqslant 300,000$. 对于前$60\%$的测试数据,满足答案小于等于$3$,$n\le

hdu 4734 数位dp

http://acm.hdu.edu.cn/showproblem.php?pid=4734 Problem Description For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1. Now you are given two numbers A and B, plea

【POJ3208】传说中POJ最难的数位DP?(正解AC自动机,二解数位DP,吾异与之)

题意: 多组数据,每组求第n个包含'666'的数(不能断开),如1:666,2:1666,14:6667. 题解: AC自动机解法没去想,数位DP没学,这里有一种类似于数位DP,却又与数位DP不同,我称为数位树. 数位树: 将数n如线段树一样地拆分成多个小段,进行递归处理得出答案. 本题详(lue)解: 直接看每一位应该是什么数,然后n减去相应的数,使得在下一层转换为子问题"在开头有b个连续的6时,求第a个带'666'的数".就是如此简单,如此简单!!!! 代码来啦! #include

POJ 3689 Apocalypse Someday [数位DP]

Apocalypse Someday Time Limit: 1000MS   Memory Limit: 131072K Total Submissions: 1807   Accepted: 873 Description The number 666 is considered to be the occult “number of the beast” and is a well used number in all major apocalypse themed blockbuster