先貼題
由於涉及小數,故源數據先乘100再加以利用。所以正常操作是開一個3百萬的數組進行dp,事實證明這會超時。
投巧的小操作
觀察到這道題的數據點,合格的發票數量不多。所以枚舉的話不會超時。
時間復雜度為:1+2+4+...+2^(n-1) = 2^n-1; 也就是 O(2^n) 的算法。明顯投機取巧了orz
vector<int> maxmoney;
maxmoney.push_back(0);
for (int i = 0; i < count; i++)
{
int maxmoney_size = maxmoney.size();
for (int j = 0; j < maxmoney_size; j++)
{
int tmp = maxmoney[j] + Value[i];
if (tmp <= sum)
{
maxmoney.push_back(tmp);
}
}
}
int res = 0;
for (auto i : maxmoney) res = max(res, i);
printf("%0.2lf\n", res / 100.0);
折半搜索
我們可以將所有發票分成兩部分,對這兩部分分別進行搜索,然后使用vector來儲存搜索過程中產生的可能的組合。最后再將兩部分進行合並,得出最終的答案。
最終的時間復雜度為 O(2^(n/2+1) + n/2 * log(n/2)) ,前者為分別搜索的時間復雜度,后者為合並的時間復雜度。
好像也沒優化多少hhh,不過 n = 40 還是可以從容應對的。
合並時,我們可以先將一部分進行排序使其有序,然后遍歷另一部分,每次進行二分搜索查找可行的答案然后取較大值。
vector<int> a, b;
int ans = 0;
dfs(0, (count - 1) / 2, 0, a); //搜索第一部分
dfs((count - 1) / 2 + 1, count - 1, 0, b); //搜索第二部分
sort(a.begin(), a.end()); //將第一部分排序,使其有序
for (int i = 0; i < b.size(); i++)
{
// ans += upper_bound(a.begin(), a.end(), sum - b[i]) - a.begin();
// //每次尋找花費比剩下的錢還要少的方案數,注意這里要使用upper_bound
////若使用lower_bound,則出現等於的情況時,方案數會有錯誤
ans = max(ans, a[upper_bound(a.begin(), a.end(), sum - b[i]) - a.begin() - 1] + b[i]);
}
printf("%0.2lf\n", ans / 100.0);
引申思考
二維情況下,比如求滿足總重量不超過 M 的條件時可累計的最大價值V,該如何處理?
完整代碼
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
#pragma warning(disable:4996)
#define int long long
char TypeA = 'A', TypeB = 'B', TypeC = 'C';
int n, m, sum, price;
double d_sum, d_price;
char type;
int Value[35];
//int dp[3000005];
void dfs(int start,int end, int total, vector<int>& R)
{
if (total > sum) return;
if (start > end)
{
R.push_back(total);
return;
}
dfs(start + 1, end, total + Value[start], R);
dfs(start + 1, end, total, R);
}
signed main() {
while (1) {
scanf("%lf%d", &d_sum, &n);
if (n == 0) break;
sum = (int)(d_sum * 100 + 0.5);
int count = 0;
memset(Value, 0, sizeof(Value));
/* memset(dp, 0, sizeof(dp));*/
for (int i = 1; i <= n; i++) {
bool flag = true;
int sum_A = 0, sum_B = 0, sum_C = 0;
int temp = 0;
scanf("%d", &m);
for (int j = 1; j <= m; j++) {
scanf(" %c:%lf", &type, &d_price); //空格 字符字符字符字符!!!
price = (int)(d_price * 100 + 0.5);
if (type == TypeA) {
sum_A += price;
temp += price;
}
else if (type == TypeB) {
sum_B += price;
temp += price;
}
else if (type == TypeC) {
sum_C += price;
temp += price;
}
else {
flag = false;
}
}
if (flag && temp <= 100000 && sum_A <= 60000 && sum_B <= 60000 && sum_C <= 60000) {
Value[count++] = temp;
}
}
vector<int> a, b;
int ans = 0;
dfs(0, (count - 1) / 2, 0, a); //搜索第一部分
dfs((count - 1) / 2 + 1, count - 1, 0, b); //搜索第二部分
sort(a.begin(), a.end()); //將第一部分排序,使其有序
for (int i = 0; i < b.size(); i++)
{
// ans += upper_bound(a.begin(), a.end(), sum - b[i]) - a.begin();
// //每次尋找花費比剩下的錢還要少的方案數,注意這里要使用upper_bound
////若使用lower_bound,則出現等於的情況時,方案數會有錯誤
ans = max(ans, a[upper_bound(a.begin(), a.end(), sum - b[i]) - a.begin() - 1] + b[i]);
}
printf("%0.2lf\n", ans / 100.0);
//for (int i = 0; i < count; i++) {
// for (int j = sum; j >= Value[i]; j--)
// dp[j] = max(dp[j], dp[j - Value[i]] + Value[i]);
//}
//double x;
//x = (dp[sum]) / 100.0; // 除 100。00 保留兩位小數
//printf("%0.2lf\n", x);
}
return 0;
}