题目: 点我
题意:桌上有4堆糖果,每堆糖果高度不超过40,每颗糖果有一种颜色(一共20种,1,2,3...,20),
有一个篮子,一开始是空的,每当里面装有两颗颜色相同的糖果时,就可以从篮子里拿出这一对糖果。
如果篮子里的糖果数量为5个并且不能再拿时,篮子充满,游戏结束。问最多能拿走多少对糖果。
糖果堆上的所有糖果拿光了也算结束
样例:
5
1 2 3 4
1 5 6 7
2 3 3 3
4 9 8 6
8 7 2 1
解释:
每堆糖果高度为5,
从左往右为4堆糖果,数字代表颜色。堆顶在上。
结果:
8
这是紫书上的一道dp习题,一年多以前看这本书时例题很多看不懂就往后翻习题,结果绝大部分感觉根本无从下手。
一开始看到这个题会觉得状态根本就无法表示,因为你需要保存每一堆的剩余层数,还需要保存篮子里的糖果数量和颜色,即使状态存下了,转移起来时间复杂度也太高了。
41^4* 21^5实在太大。
后来想想根本不用保存这么多状态。因为保存了每堆剩余的层数就知道了拿掉的糖果,又因为篮子里同种颜色的糖果只能是0或者1(同色糖会被拿掉),所以可以推出篮子里糖果的状态。
将糖果编号改为0到19,然后用状压表示每个状态(每堆剩余层数)对应篮子里的糖果。
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
#define ysk(x) (1<<(x))
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn=40 ;
int a[5][maxn+3];
int state[maxn+10][maxn+10][maxn+10][maxn+10];
int dp[maxn+3][maxn+3][maxn+3][maxn+3];
int n;
int getNum(int &s)//计算篮子s里的糖果数量
{
int cnt=0;
for(int i=0;i<20;i++) if(s&ysk(i))
{
cnt++;
}
return cnt;
}
void fix(int &x,int y)//更新dp值
{
if(y>x) x=y;
}
int update(const int* hp,const int* h,int p )//从状态hp转移到状态h,在第p堆转移。
{
int s,sp=state[hp[1] ][hp[2] ][hp[3] ][hp[4] ];
int k= a[p][hp[p]];//k表示拿掉糖果的颜色。
if(sp&ysk(k) ) { s=sp^ysk(k);
fix( dp[h[1]][h[2] ][h[3] ][h[4] ], dp[hp[1] ][hp[2] ][hp[3]][hp[4] ]+1 );
}
else{
int num=getNum(sp);
if(num==4) return 0;
s=sp^ysk(k);
fix( dp[h[1]][h[2] ][h[3] ][h[4] ], dp[hp[1] ][hp[2] ][hp[3]][hp[4] ] );
}
state[h[1]][h[2]] [h[3] ][h[4] ]=s;
return 1;
}
int work()
{
int ans=0;
state[n][n][n][n]=0;
memset(dp,-1,sizeof dp);
dp[n][n][n][n]=0;
int h[5];
for(h[1]=n;h[1]>=0;h[1]-- ){
for(h[2]=n;h[2]>=0;h[2]--){
for(h[3]=n;h[3]>=0;h[3]--){
for(h[4]=n;h[4]>=0;h[4]--){
if(h[1]+h[2]+h[3]+h[4]==4*n) continue;
// dp[h[1] ][h[2]][h[3]][h[4] ]=-1;
for(int i=1;i<=4;i++) if( h[i]+1<=n )
{
int hp[5];
memcpy(hp,h,sizeof hp);
hp[i]++;
if(dp[hp[1] ][hp[2] ][hp[3] ][hp[4] ]==-1) continue;
update(hp,h,i);
}
ans=max(ans,dp[h[1] ][h[2]][h[3]][h[4] ]);
}
}
}
}
return ans;
}
int main()
{
while(~scanf("%d",&n)&&n)
{
for(int h=n;h>=1;h-- )
{
for(int i=1;i<=4;i++)
{
scanf("%d",&a[i][h]);//第i堆,第h层糖果颜色
a[i][h]--;
}
}
printf("%d\n",work());
}
return 0;
}
/*
5
1 2 3 4
1 5 6 7
2 3 3 3
4 9 8 6
8 7 2 1
对于这组样例的分析:
如果从左往右,高度依次为 0 0 4 5 可以发现这时是个死局
因为篮子里有5颗颜色各不相同的糖果。
可以认为该状态无法达到,因为即使达到了也不能进行后续转移,并且这个状态的最后一步是没有消除糖果的,所以这个状态
相比于前面转移到它的状态并不更优,
所以说可以认为该状态达不到,这对于结果毫无影响。
假如状态(0 ,0 ,4, 5)无法达到,那么状态(0,0,4,4)还需要继续计算 ,(0,0,4,4)是可以到达的。
从结果上看,(0,0,4,4)篮子里只有4颗糖
但是(0,0,4,4)不可能从(0,0,4,5)转移过来的,完全可以从(1,0,4,4)转移过来。最后一步不同,代表着拿取糖果的顺序不同。
还有一点需要注意的是,初始化是,应该全部memset(dp,-1,sizeof dp) 否则可能会受到上一组样例的影响
比如说考虑结果(0,0,3,3),枚举这个状态的前驱,如果状态(0,0,3,4)在这个样例里是根本不可达的,甚至能达到的状态距离它
还相距甚远,但是在上一组样例里这个状态可以达到的,那么它的dp值就不为-1,会从上一个样例的状态(0,0,3,4)转移到这个样例的
(0,0,3,3),从而造成错误。
*/