1. 程式人生 > >【題解】AHOI2009同類分布

【題解】AHOI2009同類分布

efi size tin pac getc lld nbsp 一個 表示

  好開心呀~果然只有不看題解做出來的題目才會真正的有一種驕傲與滿足吧ヾ(????)?"

  實際上這題只要順藤摸瓜就可以了。首先按照數位dp的套路,有兩維想必是省不掉:1.當前dp到到的位數;2.0/1狀態表示是否受限制(這一條是因為有數字上限)。然後根據這兩個維度來接著往下想。第二個維度先撇開不看,我們只考慮如何從第 \(i - 1\) 位dp到第 \(i\) 位。在這裏其實卡了有點久,因為如果除數與被除數都在改變,那麽兩維的轉移是非常涼涼的。

  這個時候聯想題目的特殊性質 ----- 當感覺無法優化轉移 / 轉移方式的時候,考慮狀態的重新設計 & 題目的特別要求。然後很開心的發現:\(1e18\) 實際上各位數字的和最大都只有 \(162\)。那麽豈不是亂搞也可以?所以我們固定除數 \(Q\) 為 \(\left ( 1, 162 \right )\) 當中的任意一個數,分別進行dp即可。此時的轉移就簡單了,因為除數固定,自然地追加一維表示余數。狀態固定為 \(f[i][j][k][L]\),表示dp到第 \(i\) 位,要求第 \(\left ( 1, i \right )\) 位的數字之和加起來為 \(j\),且原數除以 \(Q\) 的余數為 \(k\),限制為\(L\left ( 0, 1 \right )\)的總個數。

  感覺這份代碼寫的還行,跑得也還行……能看。

#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[20], Res, mul[20];
int f[20][165][165][2];

int read()
{
    int x = 0, k = 1;
    char c;
    c = getchar();
    while(c < 0 || c > 9) { if(c == -) k = -1; c = getchar(); }
    while(c >= 
0 && c <= 9) x = x * 10 + c - 0, c = getchar(); return x * k; } #define Pre f[now][tot][rm][lim] int DP(int now, int tot, int rm, bool lim) { if(~Pre) return Pre; else Pre = 0; if(tot > now * 9) return 0; if(now == 1) { if(tot > 9 || (tot > a[now] && lim)) return
Pre = 0; return Pre = ((Pre = tot % Res == rm) ? 1 : 0); } for(int i = 0; i <= 9; i ++) { if(i > a[now] && lim) break; if(tot < i) break; int q = (mul[now] * i) % Res; q = q % Res; int L = (i == a[now] && lim); f[now][tot][rm][lim] += DP(now - 1, tot - i, (rm - q + Res) % Res, L); } return f[now][tot][rm][lim]; } #undef Pre int Solve(int x) { int k = x, ans = 0, num = 0; while(k) num ++, a[num] = k % 10, k /= 10; for(int i = 1; i <= 163; i ++) { Res = i; if(i > num * 9) continue; memset(f, -1, sizeof(f)); ans += DP(num, i, 0, 1); } return ans; } signed main() { int a = read(), b = read(); mul[1] = 1; for(int i = 2; i <= 20; i ++) mul[i] = mul[i - 1] * 10; printf("%lld\n", Solve(b) - Solve(a - 1)); return 0; }

【題解】AHOI2009同類分布