数位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 }
View Code

 

 

其它一些题目:

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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

 

!!!是比较不错,待看的题

比较还好的处理的题目:

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分形、自相似

 

你可能感兴趣的:(dp)