數位DPhtml
嗯,做爲一類比較自成一派的DP仍是要學一下。數組
其實不是很難,主要經過記憶化搜索實現。ide
用例題來說解吧。函數
第一道數位DP/洛谷三萌成就達成!spa
洛谷P3413 萌數設計
大意:求[l, r]中存在長度至少爲2的迴文子串的數的個數,對1e9+7取模。3d
r <= 10^1000code
首先能夠發現,迴文串長度爲2或3便可。htm
而後咱們就設計狀態:f[k][last][ll][0/1][0/1]表示後k位中,第k + 1位是last,第k + 2位是ll,k位以前是否已出現迴文串,k位以前是否出現過非0數(不是全是前導0),知足條件的數的個數。blog
枚舉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。