將正整數 N (1 ≤ N ≤ 1,000,000) 分拆為若干個 2 的冪次 之和(允許重複),求分拆方案數,輸出最後 9 位。
例:N=7 有 6 種:
1+1+1+1+1+1+1 1+1+1+1+1+2 1+1+1+2+2 1+1+1+4 1+2+2+2 1+2+4
設 dp[n] 為方案數。
n 為奇數:必含至少一個 1,去掉兩個 1 等價於 n-2 → dp[n] = dp[n-1](因 dp[n-1]=dp[n-2])n 為偶數:分「含 1」與「不含 1」
dp[n-2]n/2 的分拆 → dp[n/2]遞推:
dp[1] = 1; dp[2] = 2;
for i = 3..N:
if i % 2 == 1: dp[i] = dp[i-1];
else: dp[i] = (dp[i-2] + dp[i/2]) % MOD;
物品 = 1, 2, 4, 8, ... 2^k ≤ N,每種無限件,求裝滿 N 的方案數。
dp[0] = 1;
for k = 0..24: // 2^24 = 16,777,216 > 1e6
coin = 1 << k;
for i = coin..N:
dp[i] = (dp[i] + dp[i-coin]) % MOD;
#include#include #include #define MOD 1000000000 #define MAXN 1000000 int64_t dp1[MAXN+1]; int64_t dp2[MAXN+1]; int coins[25]; int main() { /* 預計算 2 的冪 */ coins[0] = 1; for (int i = 1; i < 25; i++) coins[i] = coins[i-1] << 1; /* 方法一:遞推 */ dp1[1] = 1; dp1[2] = 2; for (int i = 3; i <= MAXN; i++) { if (i & 1) dp1[i] = dp1[i-1]; else dp1[i] = (dp1[i-2] + dp1[i>>1]) % MOD; } /* 方法二:完全背包 */ memset(dp2, 0, sizeof(dp2)); dp2[0] = 1; for (int k = 0; k < 25; k++) { int c = coins[k]; if (c > MAXN) break; for (int i = c; i <= MAXN; i++) dp2[i] = (dp2[i] + dp2[i-c]) % MOD; } /* 驗證一致性 */ for (int i = 1; i <= 100; i++) if (dp1[i] != dp2[i]) printf("Mismatch at %d\n", i); /* 查詢 */ int n; while (scanf("%d", &n) == 1) printf("%lld\n", dp1[n]); return 0; }
| 方法 | 時間 | 空間 | 優勢 |
|---|---|---|---|
| 遞推公式 | O(N) | O(N) | 極快,單次預計算即可答多查詢 |
| 完全背包 | O(N log N) | O(N) | 思路通用,易擴展到其他硬幣系統 |
int64_t 足夠)2 的冪次分拆是動態規劃與數論結合的精美例題。遞推公式法 O(N) 利用奇偶性質將狀態轉移優化至常數,是競賽與工程中處理大規模查詢的首選;完全背包法 則展示了「無限硬幣找零」的通用框架。兩種思路互補,值得深入掌握。