数位dp的标志:
- 要求统计满足一定条件的数的数量(即,最终目的为计数);
- 这些条件经过转化后可以使用「数位」的思想去理解和判断;
- 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;
- 上界很大(比如 $10^{18}$),暴力枚举验证会超时。
数位dp的模板题 A - 不要62
实现非常的简单,设计一个$dp[n][0/1]$ 选每一个数字的方案都记一下即可。
#include<bits/stdc++.h>
using namespace std;
int dp[15][2],vis[15][2];
int l,r,a[15];
int dfs(int len,int six,int lim){if(len==0)return 1;if(!lim&&vis[len][six])return dp[len][six];int res=0;for(int i=(lim?a[len]:9);i>=0;i--){if(i==4)continue;if(six&&i==2)continue;res+=dfs(len-1,i==6,lim&&i==a[len]);}if(!lim){vis[len][six]=1;dp[len][six]=res;}return res;
}
int solve(int x){if(x==0)return 1;int len=0;memset(a,0,sizeof a);while(x){a[++len]=x%10;x/=10;} return dfs(len,0,1);
}
int main(){while(~scanf("%d%d",&l,&r)){if(!l&&!r)break;printf("%d\n",solve(r)-solve(l-1));}return 0;
}
C - Round Numbers
这一道题记的状态有所改变,可以记 $dp[len][x][y]$ 表示在到 $len$ 这一个位置时,0的数量和1的数量分别是 $x$ 和 $y$ 当然如果你想省时间的话就可以记 $dp[len][x]$ 代表两者的差,注意差可能是负数,需要加上长度。
D - B-number
这道题记一下前面是否有 13 除 13 的余数转移即可。
E - Balanced Number
这一个题就很有意思了。需要你枚举每一个点,作为平衡点的情况,记一下两边力矩之差即可。
时间复杂度 $O(len^2*200)$
F - XHXJ's LIS
一道好题,代码不麻烦并且有一定的思维量。
首先复习 LIS 的求法,$O(n^2)$ 的求法很简单 , 但 $O(nlogn)$ 的求法用 LIS 的特点 ,即LIS长度越长,他的最末的数越大。
而且我们发现这里的数码只有 10 个,那我们是否可以使用状压的方法,来压一下每一个数是否在LIS
中出现,在每一次转移时,往st里添加这个数(当然这一个步骤需要预处理) 即可。
G - Beautiful numbers
数感题,很容易发现,其实这个各个数位的积上限时很少的,好像只有 2520,所以就跟 D - B-number 这一题一样了,就只要最后的时候对答案检查一下即可。
因为$k \mod2520 \mod s =k\mod s$
I - 吉哥系列故事——恨7不成妻
数学推导题,推一下 $sqsum$ 与 $sum$ 和 $cnt$ 的关系即可。
K - Matches Puzzle Game
把等式改为加式,记$dp[x][b][c][flag]$ 表示剩余火柴,$b,c$ 是否到顶,是否有进位。
这样的数位dp 不算很明显,需要进行一定的转化才可以变成一般的数位dp。
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int num[]= {6,2,5,5,4,5,6,3,7,6};
int n,mod,T;
int dp[505][2][2][2];
bool vis[505][2][2][2];
int dfs(int x,bool b,bool c,bool flag) {if(x<0)return 0;//不能堆叠了if(b&&c)return x==flag*2;if(vis[x][b][c][flag])return dp[x][b][c][flag];int res=0;if(b) {for(int i=0; i<=9; i++) {res+=dfs(x-num[i]-num[(i+flag)%10],1,0,(flag+i)/10);res%=mod;if(i)res+=dfs(x-num[i]-num[(i+flag)%10],1,1,(flag+i)/10);res%=mod;}} else if(c) {for(int i=0; i<=9; i++) {res+=dfs(x-num[i]-num[(i+flag)%10],0,1,(flag+i)/10);res%=mod;if(i)res+=dfs(x-num[i]-num[(i+flag)%10],1,1,(flag+i)/10);res%=mod;}} else {for(int i=0; i<=9; i++) {for(int j=0; j<=9; j++) {int tmp=i+j+flag;res+=dfs(x-num[i]-num[tmp%10]-num[j],0,0,tmp/10);res%=mod;if(i)res+=dfs(x-num[i]-num[tmp%10]-num[j],1,0,tmp/10);res%=mod;if(j)res+=dfs(x-num[i]-num[tmp%10]-num[j],0,1,tmp/10);res%=mod;if(i&&j)res+=dfs(x-num[i]-num[tmp%10]-num[j],1,1,tmp/10);res%=mod;}}}vis[x][b][c][flag]=1;return dp[x][b][c][flag]=res;
}
//时间复杂度O(500*3*2*100)
//分别表示在 x:剩余的木棍与 A需要的木棍的差 cnt: B C的暂停个数 (B C 等价) flag上一次有没有进位
//17 14 01+11=12
//细节 从小到大的堆放
signed main() {scanf("%lld",&T);int t=0;while(T--) {memset(vis,0,sizeof vis);memset(dp,0,sizeof dp);scanf("%lld%lld",&n,&mod);printf("Case #%lld: %lld\n",++t,dfs(n-3,0,0,0));}return 0;
}
对于数位dp 有三种求的量:最一般的就是求方案数,也有求和的,需要记一下每一个数的出现的个数,用这一个辅助数组进行最终的求值,当然还有求平方和的,这需要方案数,求和数组,共需要开三个数组才可以解决,详情可看I - 吉哥系列故事——恨7不成妻。