数位DP
数位DP
与数位有关的 DP.
一般来说,无脑记忆化搜索就完事了.
直接递推不仅细节繁琐,而且还不好想.
记忆化搜索的时候要记录:
1. 当前数位.
2. 是否顶着上界.
3. 是否有前导零.
4. 计数器(为了满足题目中的条件)
luoguP4317 花神的数论题
非常经典的数位 DP.
注意如果想要采取快速幂取模的话在 DP 的时候要开 $\mathrm{long }$ $\mathrm{long}$.
不然得用欧拉定理什么的.
记忆化搜索部分:
// 从高位向低位开始 dp
ll dfs(int cur,int d,int one,int tot) {
if(cur == 0) {
return (one == tot);
}
if(f[cur][d][one][tot] != -1)
return f[cur][d][one][tot];
int lim = d ? a[cur] : 1;
f[cur][d][one][tot] = 0 ;
for(int i = 0 ; i <= lim ; ++ i) {
f[cur][d][one][tot] += dfs(cur - 1, d && (i == lim), one + (i == 1), tot);
}
return f[cur][d][one][tot];
}
完整代码:
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 60
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod=10000007;
ll f[N][2][N][N], ans[N];
int a[N], cnt;
int qpow(int x,ll y) {
int tmp=1;
for(;y;y>>=1,x=(ll)x*x%mod) {
if(y&1) tmp=(ll)tmp*x%mod;
}
return tmp;
}
// 从高位向低位开始 dp
ll dfs(int cur,int d,int one,int tot) {
if(cur == 0) {
return (one == tot);
}
if(f[cur][d][one][tot] != -1)
return f[cur][d][one][tot];
int lim = d ? a[cur] : 1;
f[cur][d][one][tot] = 0 ;
for(int i = 0 ; i <= lim ; ++ i) {
f[cur][d][one][tot] += dfs(cur - 1, d && (i == lim), one + (i == 1), tot);
}
return f[cur][d][one][tot];
}
int main() {
// setIO("input");
memset(f, -1, sizeof(f));
ll n;
scanf("%lld",&n);
while(n) {
a[++cnt] = n & 1;
n >>= 1;
}
for(int i=1;i<=50;++i) {
ans[i] = dfs(cnt, 1, 0, i);
}
int re = 1;
for(int i=1;i<=50;++i) {
re = (ll)re * qpow(i, ans[i]) % mod;
}
printf("%d\n",re);
return 0;
}
luoguP2657 [SCOI2009] windy 数
同样也是十分经典的数位 DP.
不过在这道题中就要记录是否有前导零.
因为如果有前导零的话第一位填入什么数字是没有限制的.
要注意每次算的时候要将数组什么的清空.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 12
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int f[N][N][2][2], a[N], cnt;
// 从高位向低位.
int dfs(int cur, int num, int d, int l) {
if(cur == 0) {
return 1;
}
if(f[cur][num][d][l] != -1) {
return f[cur][num][d][l];
}
int lim = d ? a[cur] : 9;
int re = 0;
for(int i = 0 ; i <= lim ; ++ i) {
if(!l) {
// 无前导 0.
// 即前面至少有一个数不是 0.
if(abs(i - num) >= 2)
re += dfs(cur - 1, i, d && (i == lim), l);
}
else {
// 含有前导 0.
re += dfs(cur - 1, i, d && (i == lim), l && (i == 0));
}
}
return f[cur][num][d][l] = re;
}
int solve(int x) {
cnt = 0 ;
memset(f, -1, sizeof(f));
while(x) {
a[++cnt] = x % 10;
x /= 10;
}
// 处理完毕 a.
// 1: 含有前导 0.
return dfs(cnt, 0, 1, 1);
}
int main() {
// setIO("input");
int L, R;
scanf("%d%d",&L,&R);
printf("%d\n",solve(R) - solve(L - 1));
return 0;
}
luoguP2602 [ZJOI2010]数字计数
令 $\mathrm{f(i,d,l,num)}$ 表示位数,是否顶上界,是否有前导0,统计数字.
令 $\mathrm{g(i,d,l)}$ 表示位数,是否顶上界,是否有前导 0 的方案数.
我们要求 $\mathrm{f}$, 但在统计的时候要用到 $\mathrm{g}$.
先记忆化搜索出所有的 $\mathrm{g}$, 再来统计 $\mathrm{f}$ 即可.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define pb push_back
#define ll long long
#define N 20
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
// f 数组:
// g 数组:方案数.
ll f[N][2][2][10], g[N][2][2];
int a[N], cnt;
// 计算 g 数组,即多少个数字满足这个条件.
// 即 cur 位,是否顶上界,是否有前导 0.
ll dfs1(int cur,int d,int l) {
if(cur == 0) {
return g[cur][d][l] = 1;
}
if(g[cur][d][l] != -1) {
return g[cur][d][l];
}
g[cur][d][l] = 0;
ll re = 0;
int lim = d ? a[cur] : 9;
for(int i = 0 ; i <= lim ; ++ i) {
re += dfs1(cur - 1, d && (i == lim), l && (i == 0));
}
return g[cur][d][l] = re;
}
ll dfs2(int cur,int d,int l,int num) {
if(cur == 0) {
return 0;
}
if(f[cur][d][l][num] != -1 )
return f[cur][d][l][num];
int lim = d ? a[cur] : 9;
f[cur][d][l][num] = 0;
ll re = 0;
for(int i = 0 ; i <= lim ; ++ i) {
ll det;
if(l == 0) {
// 无前导 0.
det = dfs2(cur-1, d&&(i==lim),0,num) + (i==num)*g[cur-1][d&&(i==lim)][0];
}
else {
if(num) {
det = dfs2(cur-1,d&&(i==lim),l&&(i==0),num)+(i==num)*g[cur-1][d&&(i==lim)][l&&(i==0)];
}
else {
det = dfs2(cur-1,d&&(i==lim),l&&(i==0),num);
}
}
re += det;
}
return f[cur][d][l][num] = re;
}
ll solve(ll x, int num) {
memset(f, -1, sizeof(f));
memset(g, -1, sizeof(g));
cnt = 0;
while(x) {
a[++cnt] = x % 10;
x /= 10;
}
// 初始:有前导 0,且有上界.
dfs1(cnt, 1, 1);
return dfs2(cnt,1,1,num);
}
int main() {
// setIO("input");
ll L, R;
scanf("%lld%lld",&L,&R);
for(int i=0;i<=9;++i) {
printf("%lld ", solve(R, i) - solve(L - 1, i));
}
return 0;
}
luoguP4124 [CQOI2016]手机号码
细节较前两道题多了一点.
由于题目中规定了 $\mathrm{11}$ 位数,所以可以先枚举最高位.
这样就无需考虑前导 $0$ 的问题了.
剩下的就是大力进行数位 $\mathrm{DP}$ 了.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 13
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int cnt, a[N];
/*
j: cur + 1 位.
k: cur + 2 位.
d: 是否顶着上界.
sta: 4/8 的状态.
o: 是否有 3 个相邻的状态.
cur : 当前还没有装呢.
*/
ll f[N][N][N][2][N][2];
ll dfs(int cur,int j,int k,int d,int sta,int o) {
if(cur == 0) {
return (o == 1);
}
if(f[cur][j][k][d][sta][o] != -1) {
return f[cur][j][k][d][sta][o];
}
f[cur][j][k][d][sta][o] = 0;
ll re = 0;
int lim = d ? a[cur] : 9;
// 向下一个放置 nex.
for(int nex = 0; nex <= lim ; ++ nex) {
if(sta == 4 && nex == 8) continue;
if(sta == 8 && nex == 4) continue;
int pp = 0;
if(sta == 4 || nex == 4) pp = 4;
if(sta == 8 || nex == 8) pp = 8;
re += dfs(cur - 1, nex, j, d && (nex == lim), pp, o || (j == k && k == nex));
}
return f[cur][j][k][d][sta][o] = re;
}
ll calc(ll x) {
cnt = 0 ;
while(x) {
a[++cnt] = x % 10;
x /= 10;
}
ll re = 0ll;
for(int i = 1; i <= a[cnt] ; ++ i) {
int j = 0;
if(i == 4) j = 4;
if(i == 8) j = 8;
memset(f, -1, sizeof(f));
re += dfs(10, i, 0, (i == a[cnt]), j, 0);
}
return re;
}
int main() {
// setIO("input");
ll L, R;
scanf("%lld%lld",&L,&R);
if(L == (ll)1e10) {
printf("%lld\n",calc(R));
}
else {
printf("%lld\n",calc(R) - calc(L - 1));
}
return 0;
}
Round Numbers 圆环数
来源:bzoj1662. [Usaco2006 Nov]Round Numbers 圆环数
和前面的题一样,用记忆化搜索解决.
数位 DP 的题用记忆化搜索不仅简单,而且非常好理解.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 35
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int a[N], cnt, f[N][2][N][N][2];
/*
cur: 当前位.
d: 是否顶着上界.
zero: 0 的个数.
one: 1 的个数
l : 是否有前导 0.
*/
int dfs(int cur, int d, int zero, int one, int l) {
if(cur == 0) {
return zero >= one;
}
if(f[cur][d][zero][one][l] != -1) {
return f[cur][d][zero][one][l];
}
f[cur][d][zero][one][l] = 0;
int re = 0;
int lim = d ? a[cur] : 1;
for(int i = 0; i <= lim ; ++ i) {
if(l == 0) {
// 并没有前导 0.
re += dfs(cur - 1, d && (i == lim), zero + (i == 0), one + (i == 1), 0);
}
else {
re += dfs(cur - 1, d && (i == lim), zero, one + (i == 1), l && (i == 0));
}
}
return f[cur][d][zero][one][l] = re;
}
int solve(int x) {
cnt = 0;
while(x) {
a[++ cnt] = x & 1;
x >>= 1;
}
memset(f, -1, sizeof(f));
return dfs(cnt, 1, 0, 0, 1);
}
int main() {
// setIO("input");
int L, R;
scanf("%d%d",&L,&R);
//printf("%d\n", solve(R));
printf("%d\n", solve(R) - solve(L - 1));
return 0;
}

浙公网安备 33010602011771号