# Lời giải bài IPARB > Tác giả `Lam` ## Đề Cho số nguyên dương $n$, mỗi dãy số nguyên dương không giảm có tổng bằng $n$ được gọi là một cách phân tích số $n$. **Yêu cầu:** Đếm số cách phân tích số $n$ $( n <= 400 )$ ## Thuật toán ngây thơ Làm giống bài IPARA và in ra số cách phân tích. [**Lời giải bài IPARA**](https://hackmd.io/@M30/B10LUutQo) ## Thuật toán full Ta sẽ dùng phương pháp quy hoạch động cho bài này. Gọi `dp(sum,last)` là số cách phân tích số `sum` sao cho số cuối cùng là `last` Gọi `next` là giá trị tiếp theo ta muốn gắn vào `(next>=last)`, ta có công thức quy hoạch động như sau: ```cpp for (int next = last; next <= n; next++) dp[sum+next][next] += dp[sum][last]; ``` ### Giải thích ![](https://i.imgur.com/nYkzjAz.png) Ta muốn thêm giá trị `next` vào sau cách phân tích hiện tại => Tổng giả trị phân tích của ta sẽ trở thành `sum+next` => Công thức: `dp(sum+next,next) = dp(sum,last)` ### Code ```cpp= for (int next = 1; next <= n; next++) dp[next][next]++; for (int sum = 1; sum <= n; sum++) for (int last = 1; last <= sum; last++) for (int next = last; next <= n; next++) dp[sum+next][next] += dp[sum][last]; int ans = 0; for (int last=1; last<=n; last++) ans += dp[n][last]; cout << ans; ``` ### Nhận xét Độ phức tạp: $O(n^3)$ Không gian: $O(n^2)$ ## Thuật toán full v2 Ta sẽ áp dụng phương pháp quy hoạch động cho bài này. Gọi `dp(sum,val)` là số cách phân tích số `sum` sao cho số cuối cùng có giá trị bé hơn hoặc bằng`val`. Ta có công thức quy hoạch động như sau: ```cpp dp[sum][val] = dp[sum][val-1] + dp[sum-val][val]; ``` ### Giải thích ![](https://i.imgur.com/cBuAmEF.png) **Trường hợp 1 (giá trị cuối cùng < `val` )** Số cách phân tích là `dp(sum,val-1)` **Trường hợp 2 (giá trị cuối cùng = `val` )** Gọi `last` là giá trị trước đó của cách phân tích ta đang xét => `last <= val` Vậy, số cách phân tích `sum` mà có giá trị cuối cùng là `val` = số cách phân tích `sum-val` mà có giá trị cuối cùng là `last <= val` = `dp(sum-val,val)` > Suy ra: `dp(sum,val) = dp(sum,val-1) + dp(sum-val,val)` ### Code ```cpp= for (int last = 0; last <= n; last++) dp[0][last] = 1; for (int sum = 1; sum <= n; sum++) for (int last = 1; last <= sum; last++) dp[sum][last] = dp[sum][last-1] + dp[sum-last][last]; int ans = dp[n][n]; cout << ans; ``` ### Nhận xét Độ phức tạp: $O(n^2)$ Không gian: $O(n^2)$ ### Code tối ưu không gian ```cpp= dp[0] = 1; for (int last = 1; last <= n; last++) for (int sum = last; sum <= n; sum++) dp[sum] += dp[sum-last]; int ans = dp[n]; cout << ans; ``` ### Nhận xét Độ phức tạp: $O(n^2)$ Không gian: $O(n)$ ## Thuật toán full v3 Ta có thể show trình implementation của mình bằng cách cho template bignum vào và thể hiện rằng mình rất pro :33. [Tìm hiểu về template bigint cho C++ (bản tiếng Anh)](https://codeforces.com/blog/entry/16380) ### Code ```cpp= #include <bits/stdc++.h> #define taskname "iparb" using namespace std; const int maxn = 410; const int mod = 10; int n; struct bignum { int m[20]; }; bignum dp[maxn]; bignum operator+ (bignum a, bignum b) { bignum c; c.m[0] = max(a.m[0],b.m[0]); int nho = 0; for (int i = 1; i <= c.m[0]; i++) { if (i > a.m[0]) a.m[i] = 0; if (i > b.m[0]) b.m[i] = 0; c.m[i] = ( a.m[i] + b.m[i] + nho ) % mod; nho = ( a.m[i] + b.m[i] + nho ) / mod; } if (nho > 0) { c.m[0]++; c.m[c.m[0]] = nho; } return c; } void print(bignum a) { for (int i = a.m[0]; i >= 1; i--) { cout << a.m[i]; } cout << '\n'; } signed main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); freopen(taskname".inp","r",stdin); freopen(taskname".out","w",stdout); cin >> n; for (int i = 1; i <=n ; i++) dp[i].m[0] = 1, dp[i].m[1] = 0; dp[0].m[0] = dp[0].m[1] = 1; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) dp[j] = dp[j] + dp[j-i]; print(dp[n]); } ``` ### Nhận xét Độ phức tạp: $O(n^2*20)$ Không gian: $O(n*20)$