P1249 最大乘積
題目描述
一個正整數一般可以分為幾個互不相同的自然數的和,如 \(3=1+2\),\(4=1+3\),\(5=1+4=2+3\),\(6=1+5=2+4\)。
現在你的任務是將指定的正整數 \(n\) 分解成若干個互不相同的自然數(也可以不分解,就是這個數字本身)的和,且使這些自然數的乘積最大。
輸入格式
只一個正整數 \(n\),(\(3 \leq n \leq 10000\))。
輸出格式
第一行是分解方案,相鄰的數之間用一個空格分開,並且按由小到大的順序。
第二行是最大的乘積。
樣例
輸入樣例 #1
10
輸出樣例 #1
2 3 5
30
解題思路
這裡取自 離散小波變換° 的動態規劃解法。
題目描述中“一個正整數一般可以分為幾個互不相同的自然數的和”,也就是將自然數 \(n\) 拆解為若干個互不相同的數進行乘法運算得到最大的分解方案,輸出分解方案及乘積結果。
那麼將其視為 \(01\) 揹包問題,即從 \(1\) 到 \(n\) 中選擇若干數使其和為 \(n\) 且乘積最大,但是揹包問題中的價值是進行加法運算得到的,這是我們需要將乘積轉換為加法運算,對數中有一個很好的性質,即 \(lna+lnb=ln(ab)\),因此我們取每個數的對數為價值,而體積為數本身,在轉移過程中記錄方案,最後根據方案進行大數乘小數的高精度運算。
C++程式碼
#include <bits/stdc++.h>
#include <sstream>
using namespace std;
const int N = 10010;
typedef long long LL;
vector<int> mul(vector<int> A, int B) {
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i++) {
if (i < A.size())
t += A[i] * B;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0)
C.pop_back();
return C;
}
vector<int> intToVector(int x) { // x > 0
vector<int> X;
while (x) {
X.push_back(x % 10);
x /= 10;
}
return X;
}
int n;
double f[N], v[N]; // v 為價值
int w[N], path[N]; // path 儲存路徑
vector<int> ans; // 儲存最終方案
int main() {
cin >> n;
for (int i = 1; i <= n; i++) { // 初始化
w[i] = i;
v[i] = log(i);
}
for (int i = 1; i <= n; i++)
for (int j = n; j >= i; j--) {
if (f[j - w[i]] + v[i] > f[j]) { // 滾動陣列
f[j] = f[j - w[i]] + v[i];
path[j] = j - w[i];
}
}
for (int i = n; i; i = path[i])
ans.push_back(i - path[i]);
sort(ans.begin(), ans.end());
vector<int> res;
res.push_back(1);
for (int i = 0; i < ans.size(); i++) {
res = mul(res, ans[i]);
cout << ans[i] << ' ';
}
cout << endl;
for (int i = res.size() - 1; i >= 0; i--)
cout << res[i];
return 0;
}