1. 程式人生 > 其它 >01揹包之折半搜尋優化

01揹包之折半搜尋優化

01揹包之折半搜尋優化

先貼題

由於涉及小數,故源資料先乘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);
  • 引申思考
    當為多重揹包時,仍然可以這麼處理。只需要利用一下二進位制優化。完全揹包暫無優化方案,所以遇到混合揹包的時候,只能正常dp完全揹包 + 二進位制優化多重揹包。
//二進位制優化
for (int i = 0; i < n; i++)
	{
		scanf("%d %d %d", &vv, &mm, &ss);
		for (int j = 1; j <= ss; j <<= 1)
		{
			v[a++] = vv * j;
			m[b++] = mm * j;
			ss -= j;
		}
		if (ss) v[a++] = vv * ss, m[b++] = mm * ss;
		/*if (ss) v[a++] = vv * ss; m[b++] = mm * ss;*/
	}
  • 完整程式碼
#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;
}