首页 > 其他 > 详细

动规-数位DP

时间:2018-09-25 19:14:47      阅读:117      评论:0      收藏:0      [点我收藏+]

某场练习赛中由于没写过数位 DP 板子(OrzOrz),只能分段打表乱搞,心态非常崩。当时想的是二分数位的每一位,这样会非常绕,可不可行不知道,但现在我还没有想出用那种二分的解法。其实是要对数字范围二分,然后 DP 验证合理的数个数就可以了。然后补练了一下几道题,感觉数位 DP 不难,主要是状态设计(是否卡边界、最高位是否从1开始、讨论到哪位)的套路吧,不知道套路先推还是有点危险哒。另外据说正统写法并没有 [最高位是否从1开始] 这一维,而是去讨论前导 0 。我觉得那样有点难想,所以就加了一维。

【ZJOI2010 Day1】数字计数

题目大意:求 \([L,\ R]\) 中, [0-9] 数码出现的次数。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

//i wei
//h 是边界
//g 从1开始
//x y已经出现的个数 
//y 求y的个数

ll f[13][2][2][13][10], ans[10];
int num[13];

ll qwq(int n, bool h, bool g, int x, int y)
{
    ll &t = f[n][h][g][x][y];
    if (~t) return t;
    if (!n) return x;
    t = 0;
    int lll = g, rrr = h ? num[n] : 9;
    for (int i = lll; i <= rrr; ++i) {
        t += qwq(n - 1, h && i == rrr, false, x + (i == y), y);
    }
    return t;
}

void getans(ll n, bool t)
{
    int top = 0;
    do num[++top] = n % 10;
    while (n /= 10);
    memset(f, -1, sizeof f);
    for (int i = 1; i <= top; ++i)
        for (int j = 0; j <= 9; ++j)
            if (t)
                ans[j] += qwq(i, i == top, true, 0, j);
            else
                ans[j] -= qwq(i, i == top, true, 0, j);
    return;
}

int main()
{
    ll A, B;
    scanf("%lld%lld", &A, &B);
    getans(B, true);
    if (A > 1)
        getans(A - 1, false);
    for (int i = 0; i <= 9; ++i)
        printf("%lld ", ans[i]);
    return 0;
}

【SCOI2009 Day1】windy数

题目大意:求 \([L,\ R]\) 区间相邻两个数字差的绝对值均 \(\ge 2\) 的数的个数。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

ll f[11][11][2][2];
int num[11];

//f[i][a][g][h]
//i
//a 上一位
//g 是边界
//h 从1开始

ll qaq(int n, int a, bool g, bool h)
{
    ll &t = f[n][a][g][h];
    if (~t) return t;
    t = 0;
    int lll = h, rrr = g ? num[n] : 9;
    if (!n)
        return t = 1;
    for (int i = lll; i <= rrr; ++i) {
        if (a != 10 && abs(i - a) < 2) continue;
        t += qaq(n - 1, i, g && i == rrr, false);
    }
    return t;
}

ll getans(ll n)
{
    int top = 0;
    memset(f, -1, sizeof f);
    do num[++top] = n % 10;
    while (n /= 10);
    ll t = 0;
    for (int i = 1; i <= top; ++i)
        t += qaq(i, 10, i == top, true);
    return t;
}

int main()
{
    ll L, R;
    scanf("%lld%lld", &L, &R);
    ll t = getans(R);
    if (L > 1) t -= getans(L - 1);
    printf("%lld\n", t);
    return 0;
}

土拨鼠猎人

题目大意:求第 \(k\) 小的特殊数。特殊数是指不满足 \(\left( [有连续两位相等] \bigwedge [有数字等于右边数字+1] \right) \bigvee [有连续三位是233]\) 的数。二分, DP 计算 \(\le\) 二分答案的合理的数个数即可。

//f[i][a][b][x][y][z]
//x 相等
//y 等于前一位+1
//z 是边界 
//g 从1开始 

#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

ll f[20][12][12][2][2][2][2];
int num[20];

ll qaq(int n, int a, int b, bool x, bool y, bool z, bool g)
{
    ll &t = f[n][a][b][x][y][z][g];
    if (~t) return t;
    if (x && y) return t = 0;
    if (!n) return t = 1;
    t = 0;
    int lll = g, rrr = z ? num[n] : 9;
    for (int i = lll; i <= rrr; ++i) {
        if (i == 3 && a == 3 && b == 2) continue;
        t += qaq(n - 1, i, a, x || i == a, y || i + 1 == a, z && i == rrr, false);
    }
    return t;
}

ll check(ll n)
{
    if (n <= 99) return n;
    memset(f, -1, sizeof f);
    int top = 0;
    do num[++top] = n % 10;
    while (n /= 10);
    ll t = 0;
    for (ll i = top; i >= 1; --i)
        t += qaq(i, 11, 11, false, false, i == top, true);
    return t;
}

int main()
{
    ll N;
    scanf("%lld", &N);
    ll l = N, r = 1e18 + 5000, ans = 1;
    while (l <= r) {
        ll mid = l + r >> 1;
        if (check(mid) >= N)
            ans = mid, r = mid - 1;
        else
            l = mid + 1;
    }
    printf("%lld\n", ans);
    return 0;
}

动规-数位DP

原文:https://www.cnblogs.com/ghcred/p/9701131.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!