# Removing Digits II https://cses.fi/problemset/task/2174 本篇文章參考: https://abc864197532.github.io/2021/09/15/cses-additional-sol/ 因為原文並沒有將細節及轉移式完整列出來,我想藉由本文章對其中的dp做較詳細的解釋,若有講解得不好,或是想要補充的可以聯絡我。 --- 首先第一步,觀察的部分我借用作者的文章先混過去 ![image](https://hackmd.io/_uploads/Sy1o6dCKa.png) ![image](https://hackmd.io/_uploads/Hk5HyFCK6.png) 簡單來說,要藉著將數字轉為x999...y,且可以用已知的步驟算出到(x-1)999...y,來解出這題 簡而言之,為完成上述工作,需要分成兩步驟 ### 預處理 先設定dp[i][j][k],i為9的個數,j為個位數,k為在那堆9之前的最大位元,這個state記錄著這個數字要變成(x-1)99999z需要的步數。 舉例來說: x99999y會轉為 i = 5個9, j = y(0<=y<10),k = maxdigit(x) 接著,設定to[i][j][k],i、j、k與上面相同,to即是要那個z 此外,特別注意到當k=0的時候沒辦法消到<0,因此只會記錄到=0 下面為一些簡單的轉移式推導 | i j k | dp | to |備註| | -------- | -------- | -------- |--- | | 0 0 0 | 0 | 0 |0到0當然是0步| | 0 1 0 |1 |0 | | |依此類推 | | | | |0 0 1 |1 |9 |9需要1步,尾數會變9| |0 1 1 |2 |9 | | |0 2 1 |2 |9 |根據這三個例子,可知只要k>j次數為1,else為2 | |依此類推| | | | 將此轉為程式 ```cpp= if(i+j+k == 0){ to[i][j][k] = 0; continue; } if(i+k == 0){ dp[i][j][k]=1,to[i][j][k]=0; continue; } if(i == 0){ if(j < k)dp[i][j][k] = 1,to[i][j][k]=(10+j-k)%10; else dp[i][j][k] = 2,to[i][j][k] = (10-k)%10; continue; } ``` 1 0 0 這直觀的代表是"90",這個數同時也可以看成xy,x為9,y為0,而中間9的數量為0。意謂他其實可以代表成0 0 9,這個數前面有記錄過了,因此,dp[1][0][0] += dp[0][0][9],尾數改為to[0][0][9]。90這時便成了81,而81可以用前面的方法再繼續做。 90 81 73 66 54 49 36 27 18 09 00 這個部分可以用一個for迴圈解決 ```cpp= int last = j; for(int l = 9; l >= 0; l--){ dp[i][j][k] += dp[i-1][last][l]; last = to[i-1][last][l]; } to[i][j][k] = last; ``` 接著到了1 0 1,與上面不同的是整個數列最少也會減掉k 舉個例子:190 190 181 173 166 154 149 136 127 118 109 100->99 可以發現最後還會多一步,若k換成其它值也會有同樣的狀況,所以程式碼稍作修改後長這樣 ```cpp= int last = j; for(int l = 9; l >= 0; l--){ dp[i][j][k] += dp[i-1][last][max(k,l)]; last = to[i-1][last][max(k,l)]; } to[i][j][k] = last; ``` ### 處理n 做完預處理,接著是把輸入的值處理成可以用我們先算好得值。 假設有一數3234 根據剛剛預處理的經驗,可以聯想3234期可以表達成0 4 3。 接著ans += dp[0][4][3]。這時3234轉為"322?",這個問號就是to[0][4][3]。 重複此步驟 3234 -> 322? -> 321? -> 320? -> 319? -> 309? -> 299? -> 199? -> 99? -> 0 理解其中的關係後就可以轉成程式碼了。 ```cpp= ll ans = 0; while(n>0){ int i = numi(n);//numi找出9的個數 int k = maxdigital(n - n%(ll)pow(10,i+1)); //maxdigital找出x999...y中的x最大位元 int j = n%10; ans += dp[i][j][k]; n -= (ll)pow(10,i+1);//x-1 n = n - n%10 + to[i][j][k];//將各位數換為to } ``` 最終程式碼 ```cpp= // __________________ // | ________________ | // || ____ || // || /\ | || // || /__\ | || // || / \ |____ || // ||________________|| // |__________________| // \###################\ // \###################\ // \ ____ \ // \_______\___\_______\ // An AC a day keeps the doctor away. #pragma GCC optimize("Ofast") #include <bits/stdc++.h> #define hutao_my_wife ios_base::sync_with_stdio(0); #define forcalors_so_cute cin.tie(0); #define ll long long #define int ll //這個int單純是因為debug時懶得找溢位的程式碼 #define pii pair<int,int> #define ff first #define ss second #define pb push_back #define eb emplace_back #define bug(A) cout<<A<<" hi\n"; using namespace std; const int M = 200005; ll dp[20][10][10]; ll to[20][10][10]; int maxdigital(ll x){ ll ans = 0; while(x){ ans = max(ans,x%10); x/=10; } return ans; } int numi(ll x){ x/=10; int ans = 0; while(x%10 == 9){ ans++; x/=10; } return ans; } void solve(){ ll n; cin>>n; ll ans = 0; while(n>0){ int i = numi(n); int k = maxdigital(n - n%(ll)pow(10,i+1)); int j = n%10; ans += dp[i][j][k]; n -= (ll)pow(10,i+1); n = n - n%10 + to[i][j][k]; } cout<<ans<<'\n'; } signed main() { hutao_my_wife forcalors_so_cute int t = 1; memset(dp,0,sizeof(dp)); for(int i = 0; i <= 18; i++){ for(int k = 0; k < 10; k++){ for(int j = 0; j < 10; j++){ if(i+j+k == 0){ to[i][j][k] = 0; continue; } if(i+k == 0){ dp[i][j][k]=1,to[i][j][k]=0;continue; } if(i == 0){ if(j < k)dp[i][j][k] = 1,to[i][j][k]=(10+j-k)%10; else dp[i][j][k] = 2,to[i][j][k] = (10-k)%10; continue; } int last = j; for(int l = 9; l >= 0; l--){ dp[i][j][k] += dp[i-1][last][max(k,l)]; last = to[i-1][last][max(k,l)]; } to[i][j][k] = last; } } } while(t--){ solve(); } } ```