参考了众神犇的题解后,AC了人生第一道数位DP。
首先很容易可以想到,设calc(X)表示1..X闭区间内的windy数,那么答案肯定是calc(B)-calc(A-1)
那么剩下就是如何计算calc(X)的问题了。
预处理DP很水:f[i][j]表示i位的数,最高位数值为j的情况下有多少个windy数,那么递推式:f[i][j]+=f[i-1][k] | k=0..9 & Abs(j-k)>=2
这题的关键是分解数据,笔者做的时候还是个蒟蒻,所以碰到一些问题一下子没搞懂,所以写在这里:比如f[3][4],表示4开头的三位数有多少个windy数,实际范围是400…499。
笔者做题的时候对对数字拆解还不怎么理解,所以在此也以2682这个数写个栗子:
首先,拆成0..999,1000…1999,2000..2999,此时2999已经超过了2682,所以对2000…2682还要细分,此时我们可以把开头的2拿掉,又变成了0…682,和2000…2682是一个效果。那么0..682再向上述过程一样拆解。
如1000…1999这个值怎么算呢?这不就是f[4][1]吗?但是注意,由于不能有前导0,所以0..999的windy数并不等于f[4][0],而是0..99,100..199······900..999 同上述方法一样处理,这个操作虽然讲解起来有点繁琐,但实际代码很简单(line26~29)
还有另外两个注意点,坑的笔者多交了两次:
1、0要特判。(否则WA的很惨还不明不白的。。。)
2、当处理到某一位的原本值已经不符合要求的时候,直接Break,否则会多计算。还是举个栗子吧:1457,在做到第3位的时候,5和前面的4已经冲突,所以接下来再处理的145..开头的数肯定都是不符合要求的,再加上去就是多算了。
- #include <stdio.h>
- #include <algorithm>
- #include <string.h>
- #include <iostream>
- using namespace std;
- #define ll long long
- ll a,b,f[11][10],d[11];
- void init(){
- cin>>a>>b;
- memset(f,0,sizeof f);
- for (int i=0;i<=9;i++)
- f[1][i]=1;
- for (int i=2;i<=10;i++)
- for (int j=0;j<=9;j++)
- for (int k=0;k<=9;k++)
- if (abs(j-k)>1) f[i][j]+=f[i-1][k];
- }
- ll calc(int x){
- if (!x) return 0; //0特判
- int ans,n;
- ans=n=0;
- while (x) d[++n]=x%10,x/=10;
- for (int i=1;i<n;i++)
- for (int j=1;j<=9;j++)
- ans+=f[i][j];
- for (int i=1;i<d[n];i++) ans+=f[n][i];
- for (int i=n-1;i>0;i–){
- for (int j=0;j<d[i];j++)
- if (abs(d[i+1]-j)>1) ans+=f[i][j];
- if (abs(d[i]-d[i+1])<=1) break; //原数有冲突时直接退出
- }
- int can=1;
- for (int i=2;i<=n;i++) //特判 x本身这个数
- if (abs(d[i]-d[i-1])<=1) {can=0;break;}
- ans+=can;
- return ans;
- }
- int main(){
- init();
- cout<<(calc(b)-calc(a-1))<<endl;
- return 0;
- }