数位DP解决的通常是在一个较大的范围内满足条件的数字数量(或者满足条件的最大数,或者某个数的数量),因为满足的要求通常与数位上的数字有关,所以可以对数字长度以及数位状态来动态规划,数位DP通常有两种写法,一种是预处理出DP数组,然后对询问进行处理,另一种则是在询问中dfs搜索时记忆化DP数组,我觉得第二种比较好实现,后面的题目也都是用的这种方法。数位统计比较需要考虑的就是数字边界的问题,也就是询问的数字的上限,因为dp数组记录的是数字长度为多长,满足什么条件的时候的方案数,而接触到边界时就不能直接用记忆过的dp数组的值,所以dfs函数一般会传参limit表示当前枚举所有数字是否会超上限来进行判断。
A - Jason的特殊爱好 FZU - 2113
之前写的,用的预处理dp数组的方法,dp[i][j]表示数字长度为i,首位为0到j的情况下1的数量。
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
using namespace std;
long long a, b;
unsigned long long dp[21][10];
unsigned long long ten[20];
void init(){
ten[0] = 1;
for (int i = 1; i <= 20; i++){ ten[i] = ten[i - 1] * 10; }
for (int i = 1; i <= 9; i++){
dp[1][i] = 1;
}
for (int i = 2; i <= 19; i++){// num of 9
dp[i][0] = dp[i - 1][9];
for (int j = 1; j <= 9; j++){// num head
dp[i][j] = dp[i][j - 1]+dp[i-1][9];
if (j == 1)dp[i][j] += ten[i-1];
}
}
}
unsigned long long solve(long long n){
unsigned long long ans = 0;
long long cnt = n, bit[25], bn = 0;
while (cnt != 0){
bit[++bn] = cnt % 10;
cnt /= 10;
}
//bit[bn + 1] = 0;
for (int i = bn; i >= 1; i--){
if (bit[i] != 0)ans += dp[i][bit[i] - 1];
if (bit[i] == 1){ ans += ((n%ten[i-1]) + 1); }
}
return ans;
}
int main(){
init();
while (cin >> a >> b){
cout << (solve(b)-solve(a-1)) << endl;
}
return 0;
}
B - K-th Nya Number HDU - 3943
统计范围内有个特定个数4和特定个数7的数字的数量,还要回答范围内第q个满足满足的数是几。用二分解决的第q大询问。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
ll dp[25][25][25];//weishu, 4num,7num
int wei[30];
ll p,q,xx;
ll x,y,cc,tot;
ll dfs(int len,int four,int seven,bool limit){
if(len==0)return four==0&&seven==0;
if(four<0||seven<0)return 0;
if(!limit&&dp[len][four][seven]!=-1)return dp[len][four][seven];
int ed=limit?wei[len]:9;
ll ans=0;
for(int i=0;i<=ed;i++){
if(i==4)ans+=dfs(len-1,four-1,seven,limit&&i==ed);
else if(i==7)ans+=dfs(len-1,four,seven-1,limit&&i==ed);
else ans+=dfs(len-1,four,seven,limit&&i==ed);
}
if(!limit)dp[len][four][seven]=ans;
return ans;
}
ll cal(ll up){
tot=0;
while(up!=0){
wei[++tot]=up%10;
up/=10;
}
ll ans=dfs(tot,x,y,1);
return ans;
}
int main(){
int T;
scanf("%d",&T);
int cas=0;
while(T--){
cas++;
printf("Case #%d:\n",cas);
memset(dp,-1,sizeof(dp));
scanf("%lld%lld%lld%lld",&p,&q,&x,&y);
ll unum=cal(q);
ll dnum=cal(p);
ll num=unum-dnum;
scanf("%d",&cc);
for(int i=0;i<cc;i++){
scanf("%lld",&xx);
if(xx>num)printf("Nya!\n");
else{
ll lef=p+1,rig=q,ans;
while(lef<=rig){
ll mid=(lef+rig)/2;
ll cnt=cal(mid)-dnum;
if(cnt==xx){ans=mid;rig=mid-1;}
else if(cnt>xx){rig=mid-1;}
else {lef=mid+1;}
}
cout<<lef<<endl;
}
}
}
return 0;
}
C - SNIBB HDU - 3271
要求统计数字在k进制下的数位和为m的数量。就是在处理上限时从整除10变成整除k就行了。
//x和y的大小是不确定的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
int dp[105][305];//weishu, 4num,7num
int wei[105];
int q,x,y,b,m,k,tot;
ll dfs(int len,int sum,bool limit,int base){
if(sum>m)return 0;
if(len==0)return sum==m;
if(!limit&&dp[len][sum]!=-1)return dp[len][sum];
int ed=limit?wei[len]:base-1;
int ans=0;
for(int i=0;i<=ed;i++){
ans+=dfs(len-1,sum+i,limit&&i==ed,base);
}
if(!limit)dp[len][sum]=ans;
return ans;
}
ll cal(int up,int base){
if(up<0)return 0;
tot=0;
while(up!=0){
wei[++tot]=up%base;
up/=base;
}
int ans=dfs(tot,0,1,base);
return ans;
}
int main(){
int cas=0;
while(~scanf("%d",&q)){
cas++;
memset(dp,-1,sizeof(dp));
scanf("%d%d%d%d",&x,&y,&b,&m);
if(x>y)swap(x,y);
if(q==2)scanf("%d",&k);
int unum=cal(y,b);
int dnum=cal(x-1,b);
int num=unum-dnum;
printf("Case %d:\n",cas);
if(q==1)printf("%d\n",num);
else{
if(k>num){printf("Could not find the Number!\n");continue;}
int lef=x,rig=y,ans;
while(lef<=rig){
int mid=(lef+(ll)rig)/2;
if(cal(mid,b)-dnum>=k){ans=mid;rig=mid-1;}
else lef=mid+1;
}
printf("%d\n",ans);
}
}
return 0;
}
D - Word Index HDU - 1336
合法要求是字典序单调增,给你序列询问你这是第几个,不合法输出0
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2000005;
int dp[10][27][2];
int wei[10];
int dfs(int len,int last,int status,int limit){
if(len==0)return status==1;
if(!limit&&dp[len][last][status]!=-1)return dp[len][last][status];
int ed=limit?wei[len]:26;
int ans=0;
if(status==0)ans+=dfs(len-1,last,0,0);
for(int i=last+1;i<=ed;i++){
ans+=dfs(len-1,i,1,limit&&i==ed);
}
if(!limit)dp[len][last][status]=ans;
return ans;
}
ll cal(char *str){
int len=strlen(str);
for(int i=1;i<=len;i++){
wei[i]=str[len-i]-'a'+1;
}
return dfs(len,0,0,1);
}
char str[10];
int main(){
memset(dp,-1,sizeof(dp));
while(~scanf("%s",str)){
int len=strlen(str);
bool f=1;
for(int i=1;i<len;i++){
if(str[i]<=str[i-1]){f=0;break;}
}
if(f==0)printf("0\n");
else printf("%d\n",cal(str));
}
return 0;
}
E - Zero’s Number HDU - 3967
求数字分成两部分相加%k==0的方案数,dp[i][j][k][l]表示剩余长度为i的段中,分界点为j,比i高的位置的和modk剩余的数字为k,前面是否全为0的方案数。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
int wei[30];
ll k,l,r;
ll dp[22][22][22][2],pw[20];
//dp[i][j][k][l]表示剩余长度为i的段中,分界点为j,比i高的位置的和modk剩余的数字为k,前面是否全为0的方案数。
ll dfs(int len,int pos,int res,int f,int limit){
if(len==0)return res==0;//最终序列的mod不为0,不满足条件
if(!limit&&dp[len][pos][res][f]!=-1)return dp[len][pos][res][f];
if(len<=pos&&f==0)return 0;//前面那段都是0了,不满足条件
ll ans=0;
int ed=limit?wei[len]:9;
for(int i=0;i<=ed;i++){
int nxt,nf=f;
if(len>pos)nxt=(ll(res)+i*pw[len-pos-1])%k;
else nxt=(ll(res)+i*pw[len-1])%k;
if(nf==0&&i>0)nf=1;//分段点前存在不为0的点
ans+=dfs(len-1,pos,nxt,nf,limit&&i==ed);
}
if(!limit)dp[len][pos][res][f]=ans;
return ans;
}
ll solve(ll v){
int tot=0;
while(v!=0){//求出每一位的数字
wei[++tot]=v%10;
v/=10;
}
ll ans=0;
for(int i=1;i<tot;i++){//枚举分段点
ans+=dfs(tot,i,0,0,1);
}
return ans;
}
int main(){
pw[0]=1;
for(int i=1;i<=18;i++)pw[i]=pw[i-1]*10;
while(~scanf("%lld%lld%lld",&l,&r,&k)){
memset(dp,-1,sizeof(dp));
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
F - Bi-peak Number HDU - 3565
两个山坡形的数字组合起来就满足要求,要求找出给定范围内数位和最大的那个数,参考的别人的解法,dp[i][j]表示当前长度为i,状态是j的情况下后续数字使满足条件的最大数位和
include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2000005;
ull a,b;
int dp[30][10][7];//bitnum lastn status
int up[30],down[30];
int tot;
//0前导 1上不能下 2能上能下 3能下或二 4上 5能上能下 6能下或结束
int dfs(int len,int last,int status,int ulimit,int dlimit){
if(len==0)return status==6?0:-1 ;
if(!ulimit&&!dlimit&&dp[len][last][status]!=-1)return dp[len][last][status];
int upp=ulimit?up[len]:9;
int dpp=dlimit?down[len]:0;
int ans=-1;
int nxt;
for(int i=dpp;i<=upp;i++){
if(status==0){if(i!=0)nxt=1;else nxt=0;}
if(status==1){if(i>last)nxt=2;else nxt=-1;}
if(status==2){if(i>last)nxt=2;else if(i<last)nxt=3;else nxt=-1;}
if(status==3){if(i>last)nxt=4;else if(i==last){if(i)nxt=4;else nxt=-1;}else nxt=3;}
if(status==4){if(i>last)nxt=5;else nxt=-1;}
if(status==5){if(i>last)nxt=5;else if(i<last)nxt=6;else nxt=-1;}
if(status==6){if(i<last)nxt=6;else nxt=-1;}
if(nxt!=-1){
int cc=dfs(len-1,i,nxt,ulimit&&i==upp,dlimit&&i==dpp);
if(cc!=-1)ans=max(ans,cc+i);
}
}
if(!ulimit&&!dlimit)dp[len][last][status]=ans;
return ans;
}
int cal(ull a,ull b){
tot=0;
while(a!=0||b!=0){
up[++tot]=b%10;
b/=10;
down[tot]=a%10;
a/=10;
}
return dfs(tot,0,0,1,1);
}
int main(){
int T;
scanf("%d",&T);
int cas=0;
memset(dp,-1,sizeof (dp));
while(T--){
cas++;
cin>>a>>b;
int ans=cal(a,b);
if(ans==-1)ans=0;
printf("Case %d: %d\n",cas,ans);
}
return 0;
}