动态规划,记忆化搜索(分享巧克力,LA 4794)

能想到是那种枚举子集的动态规划,结果写着写着成了模拟了= =。

S是位向量,代表着巧克力的集合。

dp[S]是一个vector,里面装着巧克力集合S能拼出的长方形。模拟转移方程就是枚举S的子集S0,S1,然后拼出一个长方形,然后排序去重。。。果断超时。


其实这种O不OK的问题,状态转移方程都是一旦有一种决策OK,那就OK。

问题在于状态是啥,转移方程是啥。

很容易想到dp[S][x][y]代表集合S的巧克力拼成x*y的长方形可不可以。每一种决策就是枚举S得子集S0,以及割法1<=x0<x,1<=y0<y。状态有O(xy2^n)个,决策有O((x+y)2^k)个,总时间复杂度为O(xy(x+y)3^n)。。。。。。。。。


这个时间复杂度是远远不能承受的。

我们发现,状态定义了很多,状态转移也定义了很多,但是事实上大量的空间被浪费了,因为大量的状态是不会得到的,大量的转移也是失败的。这就提供了优化的契机。


因此我们需要把状态定义得紧凑一点,然后用记忆化搜索。

观察状态,发现集合S巧克力的总面积很可能不等于x*y。所以才被浪费了大量的空间。

我们不如让每一个被搜索的状态集合S都等于x*y。那么我们就可以减少一维的空间,因为完全可以通过S与x计算出y。然后我们还发现x和y是对称的,不如令x<y。那么dp[S][x]代表集合S,是否可以拼成宽为x的长方形。而且此时S的和是x的倍数。化简了状态后转移方程也会变得简单。

首先S的和同时是x和y的倍数。

转移方程就是枚举S的子集S0,补集S1。

如果S0的和是x的倍数,那么S1的和就也是x的倍数,那么说明能对y切一刀,分成两个子状态。

y同理。

然后就看看有没有合法的子状态咯,因为尽管这样都还是有大量的空间和决策被浪费,所以用记忆化搜索。

总时间复杂度为O(x3^n)因为记忆化搜索,很多状态达不到,所以实际运算量远小于O(x3^n)。


代码

#include<bits/stdc++.h>
#define maxn 15
#define maxx 110
#define maxs (1<<15)
using namespace std;

int n,x,y,kase,ALL;
int a[maxn],d[maxs][maxx],sum[maxs];

int bitcnt(int x) {return x==0?0:bitcnt(x>>1)+(x&1);}

bool dp(int S,int x)
{
    int& ans=d[S][x];
    if(ans!=-1) return ans;
    if(bitcnt(S)==1) return ans=1;
    int y=sum[S]/x;
    for(int S0=(S-1)&S;S0;S0=(S0-1)&S)
    {
        int S1=S^S0;
        if(sum[S0]%x==0&&dp(S0,min(x,sum[S0]/x))&&dp(S1,min(x,sum[S1]/x)))
            return ans=1;
        if(sum[S0]%y==0&&dp(S0,min(y,sum[S0]/y))&&dp(S1,min(y,sum[S1]/y)))
            return ans=1;
    }
    return ans=0;
}

bool print()
{
    if(x*y!=sum[ALL]) return false;
    else return dp(ALL,min(x,y));
}

int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        scanf("%d %d",&x,&y);
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        memset(sum,0,sizeof(sum));
        memset(d,-1,sizeof(d));
        ALL=(1<<n)-1;
        for(int i=0;i<=ALL;i++)
            for(int j=0;j<n;j++)
                if(i&(1<<j)) sum[i]+=a[j];
        if(print()) printf("Case %d: Yes\n",++kase);
        else printf("Case %d: No\n",++kase);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值