引子
我不想写总结,我不想写数位DP,我不想写数位DP总结……
笑死了,上次一道数位DP(n≤1012)(n≤10^{12})(n≤1012)的题,一个蒟蒻说:“st都要大炮打?”然后O(n)。
一句实在话,这篇文章写的很烂,也就只有代码能看了。
数位DP
数位大炮,顾名思义就是用来打表的,不要疑惑,本来就是!
要使用数位DP的题目通常是求一个区间内有几个符合条件的数,一般和每个数上的每一位有关系,就比如求一个数的每一位的数的和。
洛谷-P13085 windy 数(加强版)
不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共有多少个 windy 数?(1≤a≤b≤1018)//(1≤a≤b≤10^{18})//(1≤a≤b≤1018)//没啦
那么DP数组怎么设计呢?我们可以把dp[i][j]dp[i][j]dp[i][j]设为一个iii位数当最高位是jjj时符合条件的数。那么最终的答案就是1到 b+1 的 windy 数的数量减去1到 a 的 windy 数的数量。
我们可以用一个函数表示 1 到 x 的 windy 数的数量。首先我们可以把它分成三部分。
就比如 789 ,第一部分为 1 ~ 9 和 10 ~ 99 的 windy 数的数量总和,用 dp 数组来表示就是 ∑dp[1][j]+∑dp[2][j](0<j<10)\sum dp[1][j]+\sum dp[2][j](0<j<10)∑dp[1][j]+∑dp[2][j](0<j<10);第二部分为 100 ~ 699 的 windy 数的数量,用 dp 数组来表示就是 ∑dp[3][j](0<j<7)\sum dp[3][j](0<j<7)∑dp[3][j](0<j<7);第三部分为 700 ~ 789 的 windy 数的数量,详细去代码里体会。
时间复杂度O(2e3)O(2e3)O(2e3)
#include<bits/stdc++.h>
#define int long long//懒得打long long,好吧,已经打了两个了
using namespace std;
int dp[20][20],a[20];//dp[i][j]表示一个i位数最高位是j的windy数数量
int csk(int x){//乱取的
int len=0,ans=0;
while(x>0){
a[++len]=x%10;//用一个数组把这个数的每一位存起来,方便操作
x/=10;
}
for(int i=1;i<len;i++){//第一部分,枚举是几位数
for(int j=1;j<10;j++){//枚举i位数的最高位
ans+=dp[i][j];
}
}
for(int i=1;i<a[len];i++){//第二部分,枚举len位数的最高位
ans+=dp[len][i];
}
for(int i=len-1;i>=1;i--){//第三部分,枚举每一位
for(int j=0;j<a[i];j++){//枚举小于这位数的每一种可能
if(abs(j-a[i+1])>=2){//如果j与前面一位的差值小于2
ans+=dp[i][j];//加上
}
}
if(abs(a[i+1]-a[i])<2){//用来省时,意思是当这一位和前面那一位的差小于2,那么后面的数肯定也不符合要求,所以可以直接去世
break;
}
}
return ans;//返回
}
signed main(){
for(int i=0;i<9;i++){//初始状态
dp[1][i]=1;//一位数只可能有一个windy数
}
for(int i=2;i<=19;i++){//几位数
for(int j=0;j<10;j++){//i位数最高位为j
for(int k=0;k<10;k++){//枚举i-1位的最高位
if(abs(j-k)>=2){//如果他们的差值小于2
dp[i][j]+=dp[i-1][k];//加上
}
}
}
}
int x,y;
cin>>x>>y;
cout<<csk(y+1)-csk(x);//前缀和
return 0;
}
4万+

被折叠的 条评论
为什么被折叠?



