数位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可以写成如下形式:


int dfs(int i, int s, bool e) {
    if (i==-1) return s==target_s;
    if (!e && ~f[i][s]) return f[i][s];
    int res = 0;
    int u = e?num[i]:9;
    for (int d = first?1:0; d <= u; ++d)
        res += dfs(i-1, new_s(s, d), e&&d==u);
    return e?res:f[i][s]=res;
}

其中:

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的处理)

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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 

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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的处理)

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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的个数的,数字的个数

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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求满足符号串的数字个数!!!

统计满足和指定 升降字符串 匹配的个数

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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 平衡数

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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)来处理

[cpp]   view plain copy 在CODE上查看代码片 派生到我的代码片
  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分形、自相似

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值