数位dp牛客网6题

[CQOI2016]手机号码

前导零注意(只需要一个标记)

【数位特点】x连号、有/没有x

明七暗七

二分答案

【数位特点】x的倍数、有/没有x

好朋友

前导零再注意(占dp中的一维)

【数位特点】有子串为xyz

另:【异或和】的意思就是所有答案异或

[ZJOI2010]COUNT 数字计数

之前的题都是统计数的个数,这一题统计数码出现的次数

[SCOI2009]WINDY数

【数位特点】相邻数位的差有限制

[SCOI2012]BLINKER的仰慕者

【数位特点】各个位之积为x

前面问个数,这道题问的是符合条件的数的和

超时版(ac 20%)原因在于这个代码中,对应不同K的dp数组不能重用,而数据组数最大是5000,就会超时

# include <cstdio>
# include <cstring>
# include <unordered_map>
typedef long long i64;
using namespace std;
const i64 p = 20120427;
struct Node{ i64 sum, cnt;};
Node dp[20][2][36102]; //第三维本来应该是9^18,进行映射
int b[20];
i64 m1[36102], m_cnt = 0;
i64 pow10[20];
unordered_map <i64, int >m2;
i64 K;
void init(void) //映射,用bfs更简单?
{
    pow10[0] = 1;
    for (int i=1; i<=19; i++) pow10[i] = 10*pow10[i-1];
    int L, R, lastL, lastR;
    m1[++m_cnt] = 0; m2[0] = m_cnt;
    for (int i=1; i<=9; i++) {m1[++m_cnt] = i; m2[i] = m_cnt;}
    lastL = 2, lastR = m_cnt;
    for (int i=2; i<=18; i++) //最多乘18次
    {
        L = lastL, R = lastR, lastL = R+1;
        for (int j=L; j<=R; j++)
            for (int k=2; k<=9; k++) //每次乘2~9
                if (m2[m1[j]*k] == 0) {m1[++m_cnt] = m1[j]*k; m2[m1[j]*k] = m_cnt;}
        lastR = m_cnt;
    }
    //for (int i=1; i<=50; i++) printf("%lld ", m1[i]);
}
Node dfs(int n, bool qiandao0, int mpl_inx, bool ismax)
{
    if (n == 0)
    {
        if (m1[mpl_inx] == K) return Node{0, 1};
        else return Node{0, 0};
    }

    if (!ismax && dp[n][qiandao0][mpl_inx].sum != -1) return dp[n][qiandao0][mpl_inx];

    int m = ismax ? b[n] : 9;
    Node ans, tmp;
    ans.cnt = 0;
    ans.sum = 0;
    for (int i = 0; i <= m; i++)
    {
        if (qiandao0)
        {
            if (i == 0) tmp = dfs(n-1, true, m2[1], ismax&&i==m);
            else tmp = dfs(n-1, false, m2[i], ismax&&i==m);
        }
        else tmp = dfs(n-1, false, m2[m1[mpl_inx]*i], ismax&&i==m);

        ans.cnt += tmp.cnt;
        //if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
        ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
        //printf("%lld\n", ans.sum);
    }
    if (!ismax) dp[n][qiandao0][mpl_inx] = ans;
    return ans;
}

int split(i64 n)
{
    int bit = 0;
    while (n > 0)
    {
        b[++bit] = n % 10;
        n /= 10;
    }
    return bit;
}

int main(void)
{
    init();
    i64 L, R, t;
    scanf("%lld", &t);
    while (t--)
    {
        memset(dp, -1, sizeof(dp)); //超时的原因
        scanf("%lld%lld%lld", &L, &R, &K);
        printf("%lld\n", ((dfs(split(R), true, m2[1], true).sum-dfs(split(L-1), true, m2[1], true).sum)%p+p)%p);
    }
    return 0;
}

那怎么让对于给定不同的数位之积算得的dp数组可以重复利用呢?

就是带着K算,最后不是检查数位之积是否为K,而是每枚举一位i就用K除以i,检查最后是否剩下的是1。当然,这种情况里,0就要单独考虑了。

# include <cstdio>
# include <cstring>
# include <unordered_map>
typedef long long i64;
using namespace std;
const i64 p = 20120427;
struct Node{ i64 sum, cnt;};
int b[20];
i64 pow10[20];

// 对于K!=0 
Node dp[20][2][36102]; //第三维本来应该是9^18
i64 m1[36102], m_cnt = 0;
unordered_map <i64, int >m2;

// 对于K==0 
Node dpk0[20][2][2];

void init(void) //映射,用bfs更简单?
{
    pow10[0] = 1; for (int i=1; i<=19; i++) pow10[i] = 10*pow10[i-1];
    
    int L, R, lastL, lastR;
    m1[++m_cnt] = 0; m2[0] = m_cnt;
    for (int i=1; i<=9; i++)     { m1[++m_cnt] = i; m2[i] = m_cnt; }
    lastL = 2, lastR = m_cnt;
    for (int i=2; i<=18; i++){
        L = lastL, R = lastR, lastL = R+1;
        for (int j=L; j<=R; j++)
            for (int k=2; k<=9; k++) 
                if (m2[m1[j]*k] == 0) { m1[++m_cnt] = m1[j]*k; m2[m1[j]*k] = m_cnt; }
        lastR = m_cnt;
    }
}

Node dfs_Kis0(int n, bool qiandao0, bool have0, bool ismax)
{
    if (n == 0) return Node{0, have0};

    if (!ismax && dpk0[n][qiandao0][have0].sum != -1) return dpk0[n][qiandao0][have0];

    int m = ismax ? b[n] : 9;
    Node ans = {0, 0}, tmp;
    for (int i = 0; i <= m; i++) 
    {
        tmp = dfs_Kis0(n-1, qiandao0&&i==0,  have0||(i==0 && !qiandao0), ismax&&i==m);
        
        ans.cnt += tmp.cnt;
        //if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
        ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
        //printf("%lld\n", ans.sum);
    }
    if (!ismax) dpk0[n][qiandao0][have0] = ans;
    return ans;
}

Node dfs_Knot0(int n, bool qiandao0, int res_mpl, bool ismax)
{
    if (n == 0){
        if (m1[res_mpl] == 1) return Node{0, 1};
        else return Node{0, 0};
    }

    if (!ismax && dp[n][qiandao0][res_mpl].sum != -1) return dp[n][qiandao0][res_mpl];

    int m = ismax ? b[n] : 9;
    Node ans = {0, 0}, tmp;
    for (int i = 0; i <= m; i++) 
    {
        if (!qiandao0 && i==0) continue; //K不等于0时,枚举的数位除了前导0,不可以有0
        if (qiandao0 && i==0) tmp = dfs_Knot0(n-1, true,  res_mpl, ismax&&i==m);
        else if (m1[res_mpl] % i == 0) tmp = dfs_Knot0(n-1, false, m2[m1[res_mpl]/i], ismax&&i==m);
        else continue;
        
        ans.cnt += tmp.cnt;
        //if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
        ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
        //printf("%lld\n", ans.sum);
    }
    if (!ismax) dp[n][qiandao0][res_mpl] = ans;
    return ans;
}

int split(i64 n)
{
    int bit = 0;
    while (n > 0) {
        b[++bit] = n % 10;
        n /= 10;
    }
    return bit;
}

int main(void)
{
    init();
    i64 L, R,K, t;
    scanf("%lld", &t);
    memset(dp, -1, sizeof(dp));
    memset(dpk0, -1, sizeof(dpk0));
    while (t--)
    {
        scanf("%lld%lld%lld", &L, &R, &K);
        if (K)
            printf("%lld\n", ((dfs_Knot0(split(R), true, m2[K], true).sum-dfs_Knot0(split(L-1), true, m2[K], true).sum)%p+p)%p);
        else //K==0时,就是统计含有数字0的数的和有几个,用函数dfs2计算
            printf("%lld\n", ((dfs_Kis0(split(R), true,false ,true).sum-dfs_Kis0(split(L-1), true,false, true).sum)%p+p)%p);
            
    }
    return 0;
}

突然想起一道不是给定各个位之积,而是给定各个位之lcm的题目。后补。

最后,数位dp的模板

# include <cstdio>
# include <cstring>
typedef long long i64;
using namespace std;

i64 dp[________];
int b[__];
i64 dfs(int n, ..., bool ismax)
{
    if (n == 0)
    {
        ...
    }

    if (!ismax && dp[________] != -1) return dp[________];

    int m = ismax ? b[n] : 9;
    i64 ans = 0;
    for (int i = 0; i <= m; i++)
    {
        ...
    }
    if (!ismax) dp[________] = ans;
    return ans;
}

int split(i64 n)
{
    int bit = 0;
    while (n > 0)
    {
        b[++bit] = n % 10;
        n /= 10;
    }
    return bit;
}

int main(void)
{
    memset(dp, -1, sizeof(dp));
    i64 L, R;
    scanf("%lld%lld", &L, &R);
    printf("%lld\n", dfs(split(R), ..., true)-dfs(split(L-1), ..., true));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45339670/article/details/128766893