[转]数位dp小记
转载自:http://blog.csdn.net/guognib/article/details/25472879
参考:
http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html
kuangbin :http://www.cnblogs.com/kuangbin/category/476047.html
http://blog.csdn.net/cmonkey_cfj/article/details/7798809
http://blog.csdn.net/liuqiyao_01/article/details/9109419
数位dp有递推和记忆化搜索的方法,比较来说记忆化搜索方法更好。通过博客一的一个好的模板,数位dp就几乎变成一个线性dp问题了。
下为博客一内容:
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
偷看了下7k+大牛的数位统计dp写法,通常的数位dp可以写成如下形式:
1 int dfs(int i, int s, bool e) { 2 if (i==-1) return s==target_s; 3 if (!e && ~f[i][s]) return f[i][s]; 4 int res = 0; 5 int u = e?num[i]:9; 6 for (int d = first?1:0; d <= u; ++d) 7 res += dfs(i-1, new_s(s, d), e&&d==u); 8 return e?res:f[i][s]=res; 9 }
其中:
f为记忆化数组;
i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);
s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);
e表示之前的数是否是上界的前缀(即后面的数能否任意填)。
for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。It depends.
于是关键就在怎么设计状态。当然做多了之后状态一眼就可以瞄出来。
注意:
不满足区间减法性质的话(如hdu 4376),不能用solve(r)-solve(l-1),状态设计会更加诡异。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
正如上面说的要注意:
前导零是否有影响。
是否满足区间减的性质。(如hdu4376,还没做,先记上)
几乎就是模板题:
模板:
UESTC 1307相邻的数差大于等于2
(不允许有前导0,前导0对计算有影响,注意前导0的处理)
1 int dp[15][10]; 2 int bit[15]; 3 int dfs(int pos, int s, bool limit, bool fzero) 4 { 5 if (pos == -1) return 1;///前导0的影响!!! 6 if (!limit && !fzero && ~dp[pos][s]) return dp[pos][s];///条件判断!!! 7 int end = limit ? bit[pos] : 9; 8 9 int ans = 0; 10 for (int i = 0; i <= end; i++) 11 { 12 if (!fzero && abs(i - s) < 2) continue;///前导0的影响!!! 13 int nows = i; 14 ans += dfs(pos - 1, nows, limit && (i == end), fzero && !i); 15 } 16 17 return limit || fzero ? ans : dp[pos][s] = ans;///条件判断!!! 18 } 19 int calc(int n) 20 { 21 if (n == 0) return 1; 22 int len = 0; 23 while (n) 24 { 25 bit[len++] = n % 10; 26 n /= 10; 27 } 28 return dfs(len - 1, 0, 1, 1); 29 } 30 int main () 31 { 32 memset(dp, -1, sizeof(dp)); 33 int l, r; 34 while (cin >> l >> r) 35 { 36 cout << calc(r) - calc(l - 1) << endl; 37 } 38 return 0; 39 }
其它一些题目:
Hdu3555不能出现连续的49
1 int bit[25]; 2 LL dp[25][3]; 3 /** 4 0 5 1 6 2 7 */ 8 9 /** 10 pos为当前考虑的位置 11 s为在pos之前已到达的状态 12 limit当前考虑的数字是否有限制,即之前已确定的数是否为n的前缀 13 */ 14 LL dfs(int pos, int s, bool limit) 15 { 16 if (pos == -1) return s == 2; 17 if (!limit && ~dp[pos][s]) return dp[pos][s]; 18 int end = limit ? bit[pos] : 9;///limit选择 19 LL ans = 0; 20 21 for (int i = 0; i <= end; i++) 22 { 23 int nows; 24 if (s == 0) 25 { 26 if (i == 4) nows = 1; 27 else nows = 0; 28 } 29 else if (s == 1) 30 { 31 if (i == 9) nows = 2; 32 else if (i == 4) nows = 1; 33 else nows = 0; 34 } 35 else if (s == 2) nows = 2; 36 37 ans += dfs(pos - 1, nows, limit && i == end); 38 } 39 40 return limit ? ans : dp[pos][s] = ans;///limit选择 41 } 42 43 LL calc(LL n) 44 { 45 /// 46 int len = 0; 47 while (n) 48 { 49 bit[len++] = n % 10; 50 n /= 10; 51 } 52 /// 53 return dfs(len - 1, 0, 1); 54 } 55 int main () 56 { 57 memset(dp, -1, sizeof(dp)); 58 int T; 59 RI(T); 60 LL n; 61 while (T--) 62 { 63 cin >> n; 64 cout << calc(n) << endl; 65 } 66 return 0; 67 }
hdu2089 不要62
1 int dp[10][3]; 2 int bit[10]; 3 int dfs(int pos, int s, int limit, bool first) 4 { 5 if (pos == -1) return s == 2; 6 if (!limit && ~dp[pos][s]) return dp[pos][s]; 7 int end = limit ? bit[pos] : 9; 8 // int be = first ? 1 : 0; 9 10 11 int ans = 0; 12 for (int i = 0; i <= end; i++) 13 { 14 int nows = s; 15 if (s == 0) 16 { 17 if (i == 6) nows = 1; 18 else if (i == 4) nows = 2; 19 } 20 if (s == 1) 21 { 22 if (i == 2 || i == 4) nows = 2; 23 else if (i == 6) nows = 1; 24 else nows = 0; 25 } 26 if (i == 4) nows = 2; 27 28 ans += dfs(pos - 1, nows, limit && (i == end), first && !i); 29 } 30 31 return limit ? ans : dp[pos][s] = ans; 32 } 33 int calc(int n) 34 { 35 int tmp = n; 36 if (n == 0) return 0; 37 int len = 0; 38 while (n) 39 { 40 bit[len++] = n % 10; 41 n /= 10; 42 } 43 return tmp - dfs(len - 1, 0, 1, 1); 44 } 45 int main () 46 { 47 memset(dp, -1, sizeof(dp)); 48 int l, r; 49 // for (int i = 1; i <= 100; i++) 50 // cout << i << ' ' << calc(i) << endl; 51 while (cin >> l >> r) 52 { 53 if (l + r == 0) break; 54 // cout << calc(r) << ' ' << calc(l - 1) << endl; 55 cout << calc(r) - calc(l - 1) << endl; 56 } 57 58 return 0; 59 }
UESTC 1307相邻的数差大于等于2
(注意前导0的处理)
1 int dp[15][10]; 2 int bit[15]; 3 int dfs(int pos, int s, bool limit, bool fzero) 4 { 5 if (pos == -1) return 1;///前导0的影响!!! 6 if (!limit && !fzero && ~dp[pos][s]) return dp[pos][s];///条件判断!!! 7 int end = limit ? bit[pos] : 9; 8 9 int ans = 0; 10 for (int i = 0; i <= end; i++) 11 { 12 if (!fzero && abs(i - s) < 2) continue;///前导0的影响!!! 13 int nows = i; 14 ans += dfs(pos - 1, nows, limit && (i == end), fzero && !i); 15 } 16 17 return limit || fzero ? ans : dp[pos][s] = ans;///条件判断!!! 18 } 19 int calc(int n) 20 { 21 if (n == 0) return 1; 22 int len = 0; 23 while (n) 24 { 25 bit[len++] = n % 10; 26 n /= 10; 27 } 28 return dfs(len - 1, 0, 1, 1); 29 } 30 int main () 31 { 32 memset(dp, -1, sizeof(dp)); 33 int l, r; 34 while (cin >> l >> r) 35 { 36 cout << calc(r) - calc(l - 1) << endl; 37 } 38 return 0; 39 }
POJ 3252 Round Number (组合数)!!!
拆成2进制,在记录0和1的个数
求区间[l,r]中,满足传化成2进制后,0的个数>=1的个数的,数字的个数
1 int dp[40][40][40]; 2 int bit[40]; 3 int dfs(int pos, int num0, int num1, bool limit, bool fzero) 4 { 5 if (pos == -1) return num0 >= num1;///前导0的影响!!! 6 if (!limit && !fzero && ~dp[pos][num0][num1]) return dp[pos][num0][num1];///条件判断!!! 7 int end = limit ? bit[pos] : 1; 8 9 int ans = 0; 10 for (int i = 0; i <= end; i++) 11 { 12 int new0, new1; 13 if (fzero) 14 { 15 new0 = 0, new1 = 0; 16 if (i) new1 = 1; 17 } 18 else 19 { 20 new0 = num0, new1 = num1; 21 if (i) new1++; 22 else new0++; 23 } 24 ans += dfs(pos - 1, new0, new1, limit && (i == end), fzero && !i); 25 } 26 return limit || fzero ? ans : dp[pos][num0][num1] = ans;///条件判断!!! 27 } 28 int calc(int n) 29 { 30 if (n == 0) return 1; 31 int len = 0; 32 while (n) 33 { 34 bit[len++] = n % 2; 35 n /= 2; 36 } 37 return dfs(len - 1, 0, 0, 1, 1); 38 } 39 int main () 40 { 41 memset(dp, -1, sizeof(dp)); 42 int l, r; 43 while (cin >> l >> r) 44 { 45 cout << calc(r) - calc(l - 1) << endl; 46 } 47 return 0; 48 }
hdu3886求满足符号串的数字个数!!!
统计满足和指定 升降字符串 匹配的个数
1 int dp[105][105][10]; 2 int bit[105]; 3 char s[105]; 4 char a[105], b[105]; 5 int ns; 6 7 bool ok(int r, int a, int b) 8 { 9 if (s[r] == '/') return a < b; 10 else if (s[r] == '-') return a == b; 11 else if (s[r] == '\\') return a > b; 12 } 13 int dfs(int pos, int r, int pre, bool limit, bool prezero) 14 { 15 if (pos == -1) return (r == ns); 16 if (!limit && !prezero && ~dp[pos][r][pre]) return dp[pos][r][pre]; 17 int end = limit ? bit[pos] : 9; 18 int ans = 0; 19 20 for (int i = 0; i <= end; i++) 21 { 22 if (prezero) 23 { 24 ans += dfs(pos - 1, r, i, limit && (i == end), prezero && (!i)); 25 } 26 else 27 { 28 if (r < ns && ok(r, pre, i))///优先考虑向后拓展 29 ans += dfs(pos - 1, r + 1, i, limit && (i == end), 0); 30 else if (r > 0 && ok(r - 1, pre, i)) 31 ans += dfs(pos - 1, r, i, limit && (i == end), 0); 32 } 33 ans %= MOD; 34 } 35 if (!limit && !prezero) dp[pos][r][pre] = ans; 36 return ans; 37 } 38 int calc(char a[], bool dec) 39 { 40 41 int n = strlen(a); 42 int len = 0, tmp = 0; 43 while (a[tmp] == '0') tmp++; 44 for (int i = n - 1; i >= tmp; i--) 45 { 46 bit[len++] = a[i] - '0'; 47 } 48 if (dec && len > 0) 49 { 50 for (int i = 0; i < len; i++) 51 { 52 if (bit[i]) 53 { 54 bit[i]--; 55 break; 56 } 57 else bit[i] = 9; 58 } 59 } 60 return dfs(len - 1, 0, 0, 1, 1); 61 } 62 63 int main () 64 { 65 while (scanf("%s", s) != EOF) 66 { 67 memset(dp, -1, sizeof(dp)); 68 ns = strlen(s); 69 scanf("%s%s", a, b); 70 printf("%08d\n", (calc(b, 0) - calc(a, 1) + MOD) % MOD); 71 } 72 return 0; 73 }
HDU 3709 平衡数
1 LL dp[20][20][2000]; 2 ///力矩最大为不超过10*20*10; 3 int bit[20]; 4 5 LL dfs(int pos, int r, int sum, int e) 6 { 7 if (pos == -1) return sum == 0; 8 if (sum < 0) return 0; 9 if (!e && ~dp[pos][r][sum]) return dp[pos][r][sum]; 10 int end = e ? bit[pos] : 9; 11 LL ans = 0; 12 for (int i = 0; i <= end; i++) 13 { 14 ans += dfs(pos - 1, r, sum + i * (pos - r), e && (i == end)); 15 } 16 if (!e) dp[pos][r][sum] = ans; 17 return ans; 18 } 19 20 LL calc(LL n) 21 { 22 if (n < 0) return 0; 23 int len = 0; 24 while (n) 25 { 26 bit[len++] = n % 10; 27 n /= 10; 28 } 29 LL ans = 0; 30 for (int i = 0; i < len; i++)///需要枚举支点 31 ans += dfs(len - 1, i, 0, 1); 32 return ans - (len - 1); 33 } 34 int main () 35 { 36 memset(dp, -1, sizeof(dp)); 37 int t; 38 LL l, r; 39 RI(t); 40 while (t--) 41 { 42 scanf("%I64d%I64d", &l, &r); 43 printf("%I64d\n", calc(r) - calc(l - 1)); 44 } 45 46 return 0; 47 }
HDU4352严格上升子序列的长度为K的个数。!!!
最长上升子序列结合,通过集合(1<<10)来处理
1 LL dp[25][1 << 11][11]; 2 int bit[25]; 3 int k; 4 int getnews(int s, int x) 5 { 6 for(int i=x;i<10;i++) 7 if(s&(1<<i))return (s^(1<<i))|(1<<x); 8 return s|(1<<x); 9 } 10 int getnum(int s) 11 { 12 int ret = 0; 13 while (s) 14 { 15 if (s & 1) ret++; 16 s >>= 1; 17 } 18 return ret; 19 } 20 LL dfs(int pos, int s, int e, int z) 21 { 22 if (pos == -1) return getnum(s) == k; 23 if (!e && ~dp[pos][s][k]) return dp[pos][s][k]; 24 int end = e ? bit[pos] : 9; 25 LL ans = 0; 26 27 for (int i = 0; i <= end; i++) 28 { 29 int news = getnews(s, i); 30 if (z && !i) news = 0; 31 ans += dfs(pos - 1, news, e && (i == end), z && (!i)); 32 } 33 if (!e) dp[pos][s][k] = ans; 34 return ans; 35 } 36 37 LL calc(LL n) 38 { 39 int len = 0; 40 while (n) 41 { 42 bit[len++] = n % 10; 43 n /= 10; 44 } 45 return dfs(len - 1, 0, 1, 1); 46 } 47 48 int main () 49 { 50 LL n, m; 51 memset(dp, -1, sizeof(dp)); 52 int t; 53 scanf("%d", &t); 54 int nc = 1; 55 while (t--) 56 { 57 cin >> n >> m >> k; 58 printf("Case #%d: ", nc++); 59 cout << calc(m) - calc(n - 1) << endl; 60 } 61 return 0; 62 }
!!!是比较不错,待看的题
比较还好的处理的题目:
Codeforces 55D Beautiful numbers!!!
spoj 10606 Balanced Numbers
ac自动机和数位dp结合(!!!):
hdu4376!!!(区间不可减???)
ZOJ3494 BCD Code(AC自动机+数位DP)!!!
整除和简单统计:
HDU4507 和7无关数的平方和!!!
HDU 3652 出现13,而且能被13整除
LightOJ 1068 能被K整数且各位数字之和也能被K整除的数
light OJ 1140两个数之间的所有数中零的个数。
lightoj 1032 二进制数中连续两个‘1’出现次数的和
其它:
LightOJ1205求区间[a,b]的回文数个数。
ural 1057 数位统计
codeforces215E周期数
codeforces258B在1-m中任选7个数,要使前六个数字中的“4”,"7"之和小于第七个的,
Zoj2599 数位统计(见题意)
zoj3162分形、自相似