2024.8.31雜記

Cocoicobird發表於2024-08-31

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;
}