酱神寻宝 状压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

*/

猜你喜欢

转载自blog.csdn.net/qq_39627843/article/details/81504151