1. 程式人生 > >醬神尋寶 狀壓DP

醬神尋寶 狀壓DP

醬神來到了一座小島,島上有nn個箱子。

一共有33中不同的鑰匙,金鑰匙、銀鑰匙和萬能鑰匙。醬神一開始有aa把金鑰匙、bb把銀鑰匙和cc把萬能鑰匙。

第ii個箱子上有xixi把金鎖,yiyi把銀鎖。金鑰匙只能開啟金鎖,銀鑰匙只能開啟銀鎖,萬能鑰匙兩種鎖都能開啟。用於開啟鎖的鑰匙會立刻損壞,醬神會丟掉損壞的鑰匙。箱子裡有aiai把金鑰匙、bibi把銀鑰匙和cici把萬能鑰匙,想要取出箱內的鑰匙必須要開啟這xi+yixi+yi把鎖。

醬神的目的是使他擁有的鑰匙總數最多。一旦醬神認為自己已經擁有了最多的鑰匙,他就不會去開剩下的箱子了。

Input

第一行一個數nn。

接下來有nn行。每行55個數,xi,yi,ai,bi,cixi,yi,ai,bi,ci。

最後一行3個數a,b,ca,b,c。

1=<n<=151=<n<=15

0=<xi,yi,ai,bi,ci,a,b,c<=100=<xi,yi,ai,bi,ci,a,b,c<=10

Output

輸出一個數醬神的最多鑰匙數。

Sample Input

3
1 0 0 0 1
2 4 0 8 0
3 9 10 9 8
3 1 2

1 0 0 1 2 3 0 0 0

Sample Output

8

6

Hint

第一個樣例中醬神會開啟第一個和第二個箱子。

很不幸,這道題好像交不了了。

但是,我還是看了一天的這道題,終於搞懂了一點,下面是我自己拼的思路和程式碼,都是別人的,但是我覺得是網上最好了的。

題意:

醬神來到了一座小島,島上有n個箱子。

一共有3中不同的鑰匙,金鑰匙、銀鑰匙和萬能鑰匙。醬神一開始有a把金鑰匙、b把銀鑰匙和c把萬能鑰匙。

第i個箱子上有xi把金鎖,yi把銀鎖。金鑰匙只能開啟金鎖,銀鑰匙只能開啟銀鎖,萬能鑰匙兩種鎖都能開啟。用於開啟鎖的鑰匙會立刻損壞,醬神會丟掉損壞的鑰匙。箱子裡有ai把金鑰匙、bi把銀鑰匙和ci把萬能鑰匙,想要取出箱內的鑰匙必須要開啟這xi+yi把鎖。

醬神的目的是使他擁有的鑰匙總數最多。一旦醬神認為自己已經擁有了最多的鑰匙,他就不會去開剩下的箱子了。

思路:

金鑰匙和銀鑰匙都具有指定性,只能開啟特定的鎖。所以在相同的情況下,我們儘可能地保留萬能鑰匙(貪心策略)。

則將這個策略運用於DP,我們定義狀態為 dp[st][j] 。st是一個二進位制數,利用狀壓儲存了箱子的狀態,1表示該位對應的箱子已經開啟,0表示還沒有開啟。j儲存的是當前醬神持有的金鑰匙的數目。dp儲存了當前醬神持有的最多的萬能鑰匙的數目。而銀鑰匙可以通過金鑰匙數目、萬能鑰匙數目和st的狀態計算得到。

每次狀態轉移時,從st中列舉當前為零的位數(寶箱編號),依次置一,從而得到新狀態。

下面開始添油加醋。

對於不會做的同學,看到這個題解肯定是滿臉懵逼,so do I, 但是我們至少知道了一點,dp存的是啥,知道這個再去想想,可能有點思路,但是我還是沒想出來。

我們先想想,如果直接暴力所有情況,就要n!,n的全排列種情況,肯定會超時,但是這些情況中,很多都是重複的,所以我們就可以優化一下,先有一個 2^{n} 表示不帶開箱子順序的列舉,這個怎麼來的呢,很簡單,有n個箱子,每個有0,1兩種狀態,每次開一些箱子與不開,都會形成一種狀態,但是這是不講順序的,實際上還可能存在重複的,因此我們再加一個狀態,表示醬神有的金鑰匙數目,這樣我們就可以唯一確定一種情況了。

然後演算法的步驟是:

列舉1- 2^{n} 的所有狀態,對於每一種狀態,如果有一位是1,則再考慮這一位不是1的前一個狀態,即本來這個箱子是要開的,但是這個狀態可以從前面推導,從不開這個箱子的推導,然後再考慮推導後的狀態是否可行(鑰匙是否夠)。

下面是我找到AC的一份程式碼,自己加了詳細註釋

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <string>
#include <iostream>
#include <algorithm>
#include <stack>
#include <queue>
#include <vector>
#include <deque>
#include <set>
#include <map>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define mem(a,val) memset(a,val,sizeof a)
#define mid (l+r)>>1
#define lef rt<<1
#define rig rt<<1|1
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
int n;
struct Box
{
    int need_x,need_y;  //需要的金、銀
    int newa,newb,newc; //箱子裡面的鑰匙
};
const int maxn = 1<<15+10;
Box box[20];
int num[maxn];
map<int,int> dp[maxn];  //相當於二維陣列
//和dp[i][j] 本質上一樣 ,表示在i的狀態下,當前有j把金鑰匙,dp裡面儲存萬能鑰匙數
int main()
{
    scanf("%d",&n);
    //while( scanf("%d",&n) == 1 )
    {
        for( int i = 0 ; i < (1<<n) ; i++ )
        {
            dp[i].clear();
            num[i] = 0;
        }

        fori(0,n-1)
        {
            scanf("%d %d %d %d %d",&box[i].need_x,&box[i].need_y,&box[i].newa,&box[i].newb,&box[i].newc);

        }
        int a,b,c;  //一開始的鑰匙
        scanf("%d %d %d",&a,&b,&c);
        dp[0][a] = c;
        int r = 1<<n;
        map<int,int>::iterator it;
        int ans = c;
        num[0] = a+b+c;
        for( int i = 1 ; i < r ; i++ )
        {
            num[i] = num[0];
            for( int j = 0 ; j < n ; j++ )  //判斷每一位是否為1
            {
                int t = 1<<j;
                if( t&i )
                {
                    num[i] += box[j].newa+box[j].newb+box[j].newc-box[j].need_x-box[j].need_y;
                    int pre_state = i^t;       //不放第j個物品的狀態
                    for( it = dp[pre_state].begin() ; it != dp[pre_state].end() ; it++ )
                    {
                        a = it->first;  //上一個狀態的金鑰匙數
                        c = it->second; //上一個狀態的萬能鑰匙數
                        b = num[pre_state]-a-c; //上一個狀態的銀鑰匙數

                        a -= box[j].need_x; //因為開這個箱子,所以需要消耗鑰匙
                        b -= box[j].need_y; //由此更新新的狀態

                        if( a+c >= 0 && b+c >= 0 && num[pre_state] >= box[j].need_x+box[j].need_y  ) //如果是可以開這個箱子
                        {
                            if( a < 0 ) //可能減成負,由萬能鑰匙來替代
                            {
                                c += a;
                                a = 0;
                            }
                            if( b < 0 )
                            {
                                c += b;
                                b = 0;
                            }

                            a += box[j].newa;   //得到箱子裡面的鑰匙
                            b += box[j].newb;
                            c += box[j].newc;

                            if( dp[i].find(a) != dp[i].end() )//這個地方,我覺得應該是最大max,但是別人的程式碼是min,我也無法驗證
                                dp[i][a] = min(dp[i][a],c);      //程式碼提交不了,沒辦法   
                            else dp[i][a] = c;  //相當於新的,插入一個新的
                        }
                    }
                }
            }
            if( dp[i].size() )
                ans = max(ans,num[i]);
        }
        printf("%d\n",ans);
    }
	return 0;
}
/*
100
6
1 6 8 9 9 8

*/