数位dp学习笔记
1 简介
适用题型
- 不关注数的大小;
- 经过转化后可以运用与数位有关的思想;
- 常见问法:在 \([l,r]\) 中,有多少个数满足 \(f(i)\)。
方法
我们通常使用前缀和的思想和记忆化搜索来实现数位dp。
2 常见题型
模板
就是上面所说的常见问法。
例题:windy 数
int dfs(int k,int x,int p,int q)
{
if(!k)
return 1;
if(!p&&!q&&f[k][x]!=-1)
return f[k][x];
int res=0,y=q?a[k]:9;
for(int i=0;i<=y;i++)
{
if(abs(i-x)<2)
continue;
if(p&&!i) res+=dfs(k-1,-2,1,q&&(i==y));
else res+=dfs(k-1,i,0,q&&(i==y));
}
if(!p&&!q)
f[k][x]=res;
return res;
}
这道题的难点就在于对相邻两个数字之差至少为 \(2\) 的处理,我们一开始将 \(x\) 设为 \(-2\),后面只需要判断前导 \(0\) 即可。
单独判断左端点
这类题目的数据范围通常会很大,\(l\) 和 \(r\) 需要用数组存,这时候就需要用 \(\mathrm{cnt}(1,r)-\mathrm{cnt}(1,l)\),再单独判断 \(l\) 是否符合条件。
例题:Magic Numbers
ll dfs(int k,int x,int p)
{
if(k>len)
return x?0:1;
if(!p&&f[k][x]!=-1)
return f[k][x];
int y=p?a[k]:9;
ll res=0;
if(k&1)
{
for(int i=0;i<=y;i++)
{
if(i==d)
continue;
res=(res+dfs(k+1,(x*10+i)%m,p&&(i==y)))%mod;
}
}
else
{
if(d<=y)
res=(res+dfs(k+1,(x*10+d)%m,p&&(d==y)))%mod;
}
if(!p)
f[k][x]=res;
return res;
}
这道题就是多加了被 \(m\) 整除这个条件,我们在 dfs 中用 \(x\) 表示模 \(m\) 的余数,每次变为 \((x\times 10+i)\mod m\),最后判断是否为 \(0\) 即可。
bool check(char *s)
{
len=strlen(s+1);
int x=0;
for(int i=1;i<=len;i++)
{
int y=s[i]-'0';
x=(x*10+y)%m;
if(i&1)
{
if(y==d)
return false;
}
else
{
if(y!=d)
return false;
}
}
return !x;
}
这里顺便把 check 函数也放上来了。
统计数量以外的元素
这类题目通常问的不是符合条件的数的数量,而是问符合条件的数的和等,这时候用结构体进行记忆化即可。(这类题目对数学好的选手比较友好,因为通常需要推式子)
例题:恨 7 不成妻
node dfs(int k,int x,int y,int q)
{
if(!k)
return node{x&&y,0,0};
if(!q&&~f[k][x][y].u)
return f[k][x][y];
int z=q?a[k]:9;
node res={0,0,0},tmp;
for(int i=0;i<=z;i++)
{
if(i==7)
continue;
tmp=dfs(k-1,(x+i)%7,(y*10+i)%7,q&&(i==z));
res.u=(res.u+tmp.u)%p;
res.v=(res.v+tmp.v+i*v[k-1]%p*tmp.u%p)%p;
res.w=(res.w+tmp.w+i*i%p*v[k-1]%p*v[k-1]%p*tmp.u%p+i*v[k-1]%p*tmp.v%p*2%p)%p;
}
if(!q)
f[k][x][y]=res;
return res;
}
与状压结合
这类题目通常与数字出现的次数有关,这时候我们用 \(sta\) 的二进制来进行相应的表示即可。
例题:Balanced Numbers
ll check(int x,int y)
{
for(int i=0;i<=9;i++)
{
if(y&(1<<i))
{
if((i&1)^(x&(1<<i))==0)
return 0;
}
}
return 1;
}
ll dfs(int k,int x,int y,int p,int q)
{
if(!k)
return check(x,y);
if(!p&&!q&&f[k][x][y]!=-1)
return f[k][x][y];
int z=q?a[k]:9,w;
ll res=0;
for(int i=0;i<=z;i++)
{
w=p&&!i;
res+=dfs(k-1,w?0:x^(1<<i),w?0:y|(1<<i),w,q&&(i==z));
}
if(!p&&!q)
f[k][x][y]=res;
return res;
}
注意表示出现次数的 \(x\) 用异或转移,表示是否出现过的 \(y\) 用或转移。
3 写在最后
根据个人经验,要想完全掌握数位dp,最重要的是多练习,这里推荐一份题单。

浙公网安备 33010602011771号