參考了衆神犇的題解後,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;
- }