• 数位DP


    数位DP

    嗯,作为一类比较自成一派的DP还是要学一下。

    其实不是很难,主要通过记忆化搜索实现。

    用例题来讲解吧。


    第一道数位DP/洛谷三萌成就达成!

    洛谷P3413 萌数

    大意:求[l, r]中存在长度至少为2的回文子串的数的个数,对1e9+7取模。

    r <= 10^1000

    首先可以发现,回文串长度为2或3即可。

    然后我们就设计状态:f[k][last][ll][0/1][0/1]表示后k位中,第k + 1位是last,第k + 2位是ll,k位之前是否已出现回文串,k位之前是否出现过非0数(不是全是前导0),满足条件的数的个数。

    枚举k位放什么即可。顺便更新一波状态。

    注意有limit的时候记忆化无用,要往后算。

     1 #include <cstdio>
     2 #include <algorithm>
     3 #include <cstring>
     4 
     5 const int N = 1010, MO = 1000000007;
     6 
     7 int f[N][11][11][2][3], num[N];
     8 
     9 int DFS(int k, int last, int ll, int sta, int first, bool limit) {
    10     if(k == 0) {
    11         return sta;
    12     }
    13     if(!limit && f[k][last][ll][sta][first] != -1) {
    14         return f[k][last][ll][sta][first];
    15     }
    16     int ans = 0;
    17     int large = limit ? num[k] : 9;
    18     for(int i = 0; i <= large; i++) {
    19         if(sta) {
    20             ans += DFS(k - 1, i, last, sta, first, limit && (i == num[k]));
    21             ans %= MO;
    22         }
    23         else {
    24             int temp = 0;
    25             if(first == 1) {
    26                 temp = (i == last);
    27             }
    28             else if(first == 2) {
    29                 temp = (i == last) || (i == ll);
    30             }
    31             int second = first;
    32             if(i || first) {
    33                 second = std::min(2, second + 1);
    34             }
    35             ans += DFS(k - 1, i, last, temp, second, limit && (i == num[k]));
    36             ans %= MO;
    37         }
    38     }
    39     if(!limit) {
    40         f[k][last][ll][sta][first] = ans;
    41     }
    42     //printf("%d %d %d %d %d %d = %d 
    ", k, last, ll, sta, first, limit, ans);
    43     return ans;
    44 }
    45 
    46 char str[N];
    47 
    48 int main() {
    49     memset(f, -1, sizeof(f));
    50 
    51     scanf("%s", str);
    52     int n = strlen(str);
    53     for(int i = n - 1; i >= 0; i--) {
    54         num[n - i] = str[i] - '0';
    55     }
    56     int temp = 0;
    57     if(n > 1 || num[1]) {
    58         num[1]--;
    59         int t = 1;
    60         while(num[t] < 0) {
    61             num[t] += 10;
    62             num[t + 1]--;
    63             t++;
    64         }
    65         if(!num[n]) {
    66             n--;
    67         }
    68         temp = DFS(n, 10, 10, 0, 0, 1);
    69     }
    70 
    71     scanf("%s", str);
    72     n = strlen(str);
    73     for(int i = n - 1; i >= 0; i--) {
    74         num[n - i] = str[i] - '0';
    75     }
    76    
    77     int ans = DFS(n, 10, 10, 0, 0, 1) - temp;
    78     ans = (ans % MO + MO) % MO;
    79     printf("%d", ans);
    80     return 0;
    81 }
    AC代码

    洛谷P2657 windy数

    大意:求[l, r]之中每两个相邻数码之差大于1的数的个数。

    这还比前一道题水一点......

    f[k][last][0/1]表示后k位,第k + 1位是last,k位之前是否全是0的满足条件的数的个数。

    然后就过了。

     1 #include <cstdio>
     2 #include <algorithm>
     3 #include <cstring>
     4 
     5 const int N = 20;
     6 
     7 int f[N][10][2], num[N];
     8 
     9 int DFS(int k, int last, int sta, bool limit) {
    10     if(k == 0) {
    11         return 1;
    12     }
    13     if(!limit && f[k][last][sta]) {
    14         return f[k][last][sta];
    15     }
    16 
    17     int ans = 0;
    18     int large = limit ? num[k] : 9;
    19     for(int i = 0; i <= large; i++) {
    20         if(sta) {
    21             if(i <= last + 1 && i >= last - 1) {
    22                 continue;
    23             }
    24             ans += DFS(k - 1, i, sta, limit && (i == num[k]));
    25         }
    26         else {
    27             ans += DFS(k - 1, i, i > 0, limit && (i == num[k]));
    28         }
    29     }
    30 
    31     if(!limit) {
    32         f[k][last][sta] = ans;
    33     }
    34     //printf("%d %d %d %d %d 
    ", k, last, sta, limit, ans);
    35     return ans;
    36 }
    37 
    38 inline int solve(int x) {
    39     if(!x) {
    40         return 1;
    41     }
    42     int n = 0;
    43     while(x) {
    44         num[++n] = x % 10;
    45         x /= 10;
    46     }
    47     return DFS(n, 0, 0, 1);
    48 }
    49 
    50 int main() {
    51 
    52     int a, b;
    53     scanf("%d%d", &a, &b);
    54     printf("%d", solve(b) - solve(a - 1));
    55 
    56     return 0;
    57 }
    AC代码

    注意有一个细节:这两题(大部分)中,sta状态(就是之前是否全是0)其实可以只在DFS函数中传,而不在f数组中多开一维。

    原理我还没想清楚呢......闲的没事还是加上好了,避免出锅。


    洛谷P2518 计数

    比较特立独行的一道题.....

    大意:给你若干个数码,总和<=50,让你求比给定的某一个全排列字典序小的全排列数。

    保证答案在long long内。

    就相当于给定每个数个数的数位DP。

    先开个桶。可以发现,按照上面的套路的话,!limit的时候可以直接用公式。

    然后就直接过了......我的map其实没啥用,就比暴力快了0.1ms........

    至于统计过程中爆long long...大家各显神通吧。我是分解质因数。也可以拆成若干个组合数相乘,避免除法。

      1 #include <cstdio>
      2 #include <map>
      3 #include <cstring>
      4 
      5 typedef long long LL;
      6 const int N = 60;
      7 
      8 struct STA {
      9     int a[11];
     10     inline bool operator <(const STA &w) const {
     11         for(int i = 0; i <= 10; i++) {
     12             if(a[i] != w.a[i]) {
     13                 return a[i] < w.a[i];
     14             }
     15         }
     16         return 0;
     17     }
     18     inline bool operator ==(const STA &w) const {
     19         for(int i = 0; i <= 10; i++) {
     20             if(a[i] != w.a[i]) {
     21                 return 0;
     22             }
     23         }
     24         return 1;
     25     }
     26 }bin;
     27 
     28 std::map<STA, LL> mp;
     29 int p[N], cnt[N], top, num[N];
     30 char str[N];
     31 bool vis[N];
     32 std::map<STA, LL>::iterator it;
     33 
     34 inline LL qpow(LL a, LL b) {
     35     LL ans = 1;
     36     while(b) {
     37         if(b & 1) {
     38             ans *= a;
     39         }
     40         a *= a;
     41         b = b >> 1;
     42     }
     43     return ans;
     44 }
     45 
     46 inline void getp(int b) {
     47     for(int i = 2; i <= b; i++) {
     48         if(!vis[i]) {
     49             p[++top] = i;
     50         }
     51         for(int j = 1; j <= top && i * p[j] <= b; j++) {
     52             vis[i * p[j]] = 1;
     53             if(i % p[j] == 0) {
     54                 break;
     55             }
     56         }
     57     }
     58     return;
     59 }
     60 
     61 inline void add(int x, int v) {
     62     //printf("add : %d %d 
    ", x, v);
     63     for(int i = 1; i <= top && x > 1; i++) {
     64         while(x % p[i] == 0) {
     65             cnt[i] += v;
     66             x /= p[i];
     67         }
     68     }
     69     return;
     70 }
     71 
     72 inline LL cal(int k) {
     73     memset(cnt, 0, sizeof(cnt));
     74     for(int i = 2; i <= k; i++) {
     75         add(i, 1);
     76     }
     77     for(int i = 0; i <= 9; i++) {
     78         for(int j = 2; j <= bin.a[i]; j++) {
     79             add(j, -1);
     80         }
     81     }
     82     LL ans = 1;
     83     for(int i = 1; i <= top; i++) {
     84         if(cnt[i]) {
     85             ans *= qpow(p[i], cnt[i]);
     86         }
     87     }
     88     return ans;
     89 }
     90 
     91 LL DFS(int k, bool limit) {
     92     if(k == 0) {
     93         return 1;
     94     }
     95     it = mp.find(bin);
     96     if(!limit && it != mp.end()) {
     97         return it->second;
     98     }
     99     if(!limit) {
    100         LL ans = cal(k);
    101         mp[bin] = ans;
    102         return ans;
    103     }
    104     int large = limit ? num[k] : 9;
    105     LL ans = 0;
    106     for(int i = 0; i <= large; i++) {
    107         if(!bin.a[i]) {
    108             continue;
    109         }
    110         bin.a[i]--;
    111         ans += DFS(k - 1, limit && (i == num[k]));
    112         bin.a[i]++;
    113     }
    114     /*printf("%d %d %lld 
    ", k, limit, ans);
    115     for(int i = 0; i <= 9; i++) {
    116         printf("%d ", bin.a[i]);
    117     }
    118     puts("");*/
    119     return ans;
    120 }
    121 
    122 int main() {
    123     getp(50);
    124     scanf("%s", str);
    125     int n = strlen(str);
    126     for(int i = n - 1; i >= 0; i--) {
    127         num[n - i] = str[i] - '0';
    128         bin.a[num[n - i]]++;
    129     }
    130 
    131     printf("%lld
    ", DFS(n, 1) - 1);
    132     return 0;
    133 }
    AC代码

    洛谷P2602 数字计数

    大意:求在[l, r]之中每个数码出现了几次。

    首先简化它,对每个数码分别做。

    这里的状态是f[k][0/1]表示在后k位是否中含有数码aim的个数。aim是枚举的。

    然后我们发现,如果我们当前选的数码恰好是aim,那么额外加上后面的总个数就行了。

     1 #include <cstdio>
     2 #include <cstring>
     3 
     4 typedef long long LL;
     5 const int N = 20;
     6 
     7 LL f[N][2], pw[N], s;
     8 int num[N], aim;
     9 
    10 LL DFS(int k, int sta, bool limit) {
    11     if(k == 0) {
    12         return sta == 0 && aim == 0;
    13     }
    14     if(!limit && f[k][sta] != -1) {
    15         return f[k][sta];
    16     }
    17     LL ans = 0;
    18     int large = limit ? num[k] : 9;
    19     for(int i = 0; i <= large; i++) {
    20         if(sta) {
    21             ans += DFS(k - 1, sta, limit && (i == num[k]));
    22         }
    23         else {
    24             ans += DFS(k - 1, i > 0, limit && (i == num[k]));
    25         }
    26         if(i == aim && (sta || i)) {
    27             if(limit && i == num[k]) {
    28                 ans += s % pw[k - 1] + 1;
    29             }
    30             else {
    31                 ans += pw[k - 1];
    32             }
    33         }
    34     }
    35     if(!limit) {
    36         f[k][sta] = ans;
    37     }
    38     return ans;
    39 }
    40 
    41 inline LL solve(LL x) {
    42     if(!x) {
    43         return !aim;
    44     }
    45     int n = 0;
    46     s = x;
    47     while(x) {
    48         num[++n] = x % 10;
    49         x /= 10;
    50     }
    51     return DFS(n, 0, 1);
    52 }
    53 
    54 int main() {
    55     LL a, b;
    56     scanf("%lld%lld", &a, &b);
    57     pw[0] = 1;
    58     for(int i = 1; i <= 15; i++) {
    59         pw[i] = pw[i - 1] * 10;
    60     }
    61 
    62     for(int i = 0; i <= 9; i++) {
    63         aim = i;
    64         memset(f, -1, sizeof(f));
    65         printf("%lld ", solve(b) - solve(a - 1));
    66     }
    67     return 0;
    68 }
    AC代码

    参考资料:红太阳的博客。

    总结一下:基本都是套路......做的都是板题.......

    哇!他们考的都好难啊.....要命哇...

    CF747F:一个比较独特的数位DP。

  • 相关阅读:
    工程结构
    生活决策
    工作原则概要与列表
    生活原则概要与列表
    在Windows2008下安装SQL Server 2005无法启动服务的解决办法
    MySQL启动提示High Severity Error解决方案
    知乎页面颜色个性化修改
    博客园 CodingLife 模板 翻页样式美化方法
    【翻译】GitHub Pages Basics 基本使用帮助【一】GitHub Pages 是什么?
    【翻译】GitHub Pages Basics 基本使用帮助【首页】
  • 原文地址:https://www.cnblogs.com/huyufeifei/p/10009452.html
Copyright © 2020-2023  润新知