数位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 \n", 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 }
洛谷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 \n", 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 }
注意有一个细节:这两题(大部分)中,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 \n", 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 \n", 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\n", DFS(n, 1) - 1); 132 return 0; 133 }
洛谷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 }
参考资料:红太阳的博客。
总结一下:基本都是套路......做的都是板题.......
哇!他们考的都好难啊.....要命哇...
CF747F:一个比较独特的数位DP。