uva 10118 Free Candies dp(分析+状压)


题目:  点我                               


题意:桌上有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),从而造成错误。
*/



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值