数位DP统计->手机号码(洛谷4124)
https://www.luogu.com.cn/problem/P4124
题意:给区间[l, r], 求满足条件的手机号。条件:4,8不同时出现,有AAA数字出现,没有前导0。
分析:没有前导0,如果[1,x],x < 1e11, 直接return 0。 前缀差分,高位到低位,状态是前两位数字跟当前的4,8,连续状态。 数位dp。
/*
这么设状态,是因为假如我们的pos一共有11位,现在在第7位。虽然第pos = 8, pre = a, sec_pre =b已经被计算过了,但是第11位是什么,我们不知道,
我们必须定义4出现跟8出现的情况才能确保第11位的数字出现,跟我们之前计算过的状态不冲突(也就是说之前计算的状态是可用的),如果之前计算的时候第11位是4,
但是现在第11位是2,那么之前计算的状态就失效了。 动态规划的核心是历史计算结果避免重复计算,但是我们现在要利用的结果如果不能保证跟历史计算的结果 状态完全相同,
那么这个结果就不能利用了。
*/
void solve(){
long long a, b;
cin >> a >> b;
long long dp[20][20][20][2][2][2];
vector<int> num;
function<long long(int, int, int, bool, bool, bool, bool, bool)> dfs = [&](int pos, int pre, int sec_pre, bool lead, bool limit, bool has8, bool has4, bool con)->long long{
// cout << pos << " " << pre << " " << sec_pre << endl;
if (has8 && has4){
return 0;
}
if (pos == 0){
return con;
}
if (limit == false && dp[pos][pre][sec_pre][con][has8][has4] != -1){
return dp[pos][pre][sec_pre][con][has8][has4];
}
long long res = 0;
int up = (limit == true ? num[pos] : 9);
for (int i = 0; i <= up; ++i){
if (lead == true && i == 0){
continue;
}
res += dfs(pos - 1, i, pre, false, limit && (i == up), has8 || (i == 8), has4 || (i == 4), con || (i == pre && i == sec_pre));
}
if (!limit){
dp[pos][pre][sec_pre][con][has8][has4] = res;
}
return res;
};
auto cal = [&](long long x)->long long{
num.resize(1);
while (x){
num.emplace_back(x % 10);
x /= 10;
}
memset(dp, -1, sizeof(dp));
if (int(num.size()) != 12){
return 0;
}
return dfs(int(num.size()) - 1, 0, 0, true, true, false, false, false);
};
cout << cal(b) - cal(a - 1) << '\n';
}

浙公网安备 33010602011771号