数位dp(洛谷 P4124 [CQOI2016]手机号码)

Posted Kalzn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数位dp(洛谷 P4124 [CQOI2016]手机号码)相关的知识,希望对你有一定的参考价值。

题目链接
普通数位dp,不过有几点值得深思。
首先,我们每个手机号的位数固定为11位,即,不满足11位的并不是手机号不满足条件。而且如果像普通数位dp那样求该数到0之间的数量的话,还要考虑前导0的印象。(多开一维数组存前导0)不太值得。所以,我们直接求该数到1e10之间的数:

    int mx = flag?su[cur]:9;
    int bon = (cur==len-1)?1:0;
    ll ans = 0;
    for (int i = bon; i<=mx; i++)
    
    //...........
    

此后我们设:

dp[i][p1][p2][sg1][sg2] 为考虑前i位,最后一个数字为p2,倒数第二个数字为p1,已有三个连续数相等(sg1==1)、没有三个连续数相等(sg1 ==0)的情况。其中8、4存在的情况由sg2决定:

sg2 == 0 无4、无8
sg2 == 1 有4、无8
sg2 == 2 无4、有8
sg2 == 3 有4、有8

然后就是标准数位dp过程
下面是ac代码:
压行版:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <map>
#include <vector>
#define ll long long
using namespace std;
int dp[20][11][11][5][5];
int su[20];
int len;
ll dfs(int cur, int pre1, int pre2, int sg1, int sg2, bool flag)

    //cout << cur << " " << pre1 << " " << pre2 <<" " << sg1 << " " <<sg2 << endl;
    if (cur < 0)
        return sg1 && sg2 != 3;
    if (!flag&&dp[cur][pre1][pre2][sg1][sg2] != -1) return dp[cur][pre1][pre2][sg1][sg2];
    int mx = flag?su[cur]:9;
    int bon = (cur==len-1)?1:0;
    ll ans = 0;
   // cout << bon <<" " << mx << endl;
    for (int i = bon; i<=mx; i++)
    
        ans += dfs(cur-1, pre2, i, sg1||(i == pre1 && i == pre2), sg2|(i==4||i==8?(1<<(i/4-1)):0), flag&&i==mx);//转移
    
    if (!flag) dp[cur][pre1][pre2][sg1][sg2] = ans;
    return ans;

ll so(ll n)

    len = 0;
    while(n)
    
        su[len++] = n%10;
        n /= 10;
    
    if (len != 11) return 0;//注意特判,因为这里wa了一发,因为l-1可能是9999999999.
    return dfs(len-1, 10, 10, 0, 0, 1);

int main()

    ll r, l;
    scanf("%lld%lld", &l, &r);
    memset(dp, -1, sizeof(dp));
    printf("%lld\\n", so(r) - so(l-1));

放飞自我版:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <map>
#include <vector>
#define ll long long
using namespace std;
int dp[20][11][11][5][5];
int su[20];
int len;
ll dfs(int cur, int pre1, int pre2, int sg1, int sg2, bool flag)

    //cout << cur << " " << pre1 << " " << pre2 <<" " << sg1 << " " <<sg2 << endl;
    if (cur < 0)
        return sg1 && sg2 != 3;
    if (!flag&&dp[cur][pre1][pre2][sg1][sg2] != -1) return dp[cur][pre1][pre2][sg1][sg2];
    int mx = flag?su[cur]:9;
    int bon = (cur==len-1)?1:0;
    ll ans = 0;
   // cout << bon <<" " << mx << endl;
    for (int i = bon; i<=mx; i++)
    
        int nowsg2;
        if (i == 4)
        
            if (sg2 == 0) nowsg2 = 1;
            else if (sg2 == 1) nowsg2 = 1;
            else if (sg2 == 2) nowsg2 = 3;
            else nowsg2 = 3;
        
        else if (i == 8)
        
            if (sg2 == 0) nowsg2 = 2;
            else if (sg2 == 1) nowsg2 = 3;
            else if (sg2 == 2) nowsg2 = 2;
            else nowsg2 = 3;
        
        else nowsg2 = sg2;//找当前sg2
        int nowsg1;
        if (sg1 == 1) nowsg1 = 1;
        else
        
            if (cur == len-1 || cur == len-2) nowsg1 = 0;
            else
            
                if (i == pre1 && i == pre2) nowsg1 = 1;
                else nowsg1 = 0;
            
        //找当前sg1
        ans += dfs(cur-1, pre2, i, nowsg1, nowsg2, flag&&i==mx);//转移
    
    if (!flag) dp[cur][pre1][pre2][sg1][sg2] = ans;
    return ans;

ll so(ll n)

    len = 0;
    while(n)
    
        su[len++] = n%10;
        n /= 10;
    
    if (len != 11) return 0;//注意特判,因为这里wa了一发,因为l-1可能是9999999999.
    return dfs(len-1, 0, 0, 0, 0, 1);

int main()

    ll r, l;
    scanf("%lld%lld", &l, &r);
    memset(dp, -1, sizeof(dp));
    ll ansr = so(r);
    ll ansl = so(l-1);
    printf("%lld\\n", ansr - ansl);

以上是关于数位dp(洛谷 P4124 [CQOI2016]手机号码)的主要内容,如果未能解决你的问题,请参考以下文章

P4124 [CQOI2016]手机号码

BZOJ 4521 CQOI 2016 手机号码 数位DP

题解Luogu P4121 [CQOI2016]手机号码 数位DP

4521: [Cqoi2016]手机号码|数位DP

题解Luogu P4121 [CQOI2016]手机号码

[CQOI2016]手机号码