能想到是那种枚举子集的动态规划,结果写着写着成了模拟了= =。
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;
}