酱神来到了一座小岛,岛上有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的全排列种情况,肯定会超时,但是这些情况中,很多都是重复的,所以我们就可以优化一下,先有一个 表示不带开箱子顺序的枚举,这个怎么来的呢,很简单,有n个箱子,每个有0,1两种状态,每次开一些箱子与不开,都会形成一种状态,但是这是不讲顺序的,实际上还可能存在重复的,因此我们再加一个状态,表示酱神有的金钥匙数目,这样我们就可以唯一确定一种情况了。
然后算法的步骤是:
枚举1- 的所有状态,对于每一种状态,如果有一位是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
*/