P4124 [CQOI2016]*(数位DP,前导0)

求n到m之间的吉利手机号,号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8和 4。

这个题需要考虑两个点,一个是前导0,因为有前导0的必然不是手机号,一个是长度必须是11,否则也不是手机号

至于前导0,可以额外开一个维度,记录当前是否有前导0,但是会时内存x2,本题还不会爆,但是别的题就不一定了:

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
#define int LL
int num[32], cnt, dp[32][15][15][2][2][2][2][15];

int dfs(int pos, int pre1, int pre2,int have8,int have4,int state,int limit,int pre0,int len) {
    //pos为dp的第一维 stat为第二维 limit代表当前枚举数字是否达到上限
    if (pos == -1){
        if (len != 11) return 0;
        if (state && !(have8 && have4)) return 1;
        else return 0;
    }
    if (!limit &&(pre1!=-1)&&(pre2!=-1)&& dp[pos][pre1][pre2][have8][state][have4][pre0][len] != -1) return dp[pos][pre1][pre2][have8][state][have4][pre0][len] ;
    //如果没有达到上限,即可以随便枚举,且枚举的情况已经记录过了,就直接返回
    int up;
    if (limit) //如果前面都达到上限了,那么这次也必须只能枚举到上限
        up = num[pos];
    else
        up = 9;
    int res = 0;
    for (int i = 0; i <= up; i++) {
        if (have8 && (i == 4)) continue;
        if (have4 && (i == 8)) continue;
        res += dfs(pos - 1, pre2,i,have8|(i==8),have4|(i==4), state|(pre1==pre2&&pre2==i),limit && i == num[pos],pre0&&(i==0),pre0&&(i==0)?0:len+1);
    }
    if (!limit&&(pre1!=-1)&&(pre2!=-1)) dp[pos][pre1][pre2][have8][state][have4][pre0][len] = res;
    return res;
}

int solve(LL n) {
    cnt = 0;
    while (n) {
        num[cnt++] = n % 10;
        n /= 10;
    }
    return dfs(cnt - 1,-1,-1, 0,0,0, 1,1,0); //第一个数必须由上限来限制,所以limit=1
}

signed main() {
    LL n, m;
    scanf("%lld%lld", &n, &m);
    memset(dp, -1, sizeof dp);
    printf("%lld
", solve(m) - solve(n - 1));
    return 0;
}

还有一种写法是直接在枚举当前位置上的数的时候,可以判断是否是第一位,如果是第一位,那么就只能从1开始:

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
#define int LL
int num[32], cnt, dp[32][15][15][2][2][2];

int dfs(int pos, int pre1, int pre2,int have8,int have4,int state,int limit) {
    //pos为dp的第一维 stat为第二维 limit代表当前枚举数字是否达到上限
    if (pos == -1){
        if (state && !(have8 && have4)) return 1;
        else return 0;
    }
    if (!limit &&(pre1!=-1)&&(pre2!=-1)&& dp[pos][pre1][pre2][have8][state][have4] != -1) return dp[pos][pre1][pre2][have8][state][have4] ;
    //如果没有达到上限,即可以随便枚举,且枚举的情况已经记录过了,就直接返回
    int up;
    if (limit) //如果前面都达到上限了,那么这次也必须只能枚举到上限
        up = num[pos];
    else
        up = 9;
    int res = 0;
    for (int i = (pos==10)?1:0; i <= up; i++) {
        if (have8 && (i == 4)) continue;
        if (have4 && (i == 8)) continue;
        res += dfs(pos - 1, pre2,i,have8|(i==8),have4|(i==4), state|(pre1==pre2&&pre2==i),limit && i == num[pos]);
    }
    if (!limit&&(pre1!=-1)&&(pre2!=-1)) dp[pos][pre1][pre2][have8][state][have4] = res;
    return res;
}

int solve(LL n) {
    cnt = 0;
    while (n) {
        num[cnt++] = n % 10;
        n /= 10;
    }
    if (cnt != 11) return 0;
    return dfs(cnt - 1,-1,-1, 0,0,0, 1); //第一个数必须由上限来限制,所以limit=1
}

signed main() {
    LL n, m;
    scanf("%lld%lld", &n, &m);
    memset(dp, -1, sizeof dp);
    printf("%lld
", solve(m) - solve(n - 1));
    return 0;
}
原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14418643.html