一、题目
题目描述
有 n n n个数 a i a_i ai,还有 m m m个容器,第一步把 a 1 a_1 a1赋值给某一个容器,然后每一步的赋值操作就是选两个容器的和赋值给一个容器(随便选,不要求容器不同,但求和容器必须有值),第 i i i步赋值操作的返回值必须等于 a i a_i ai(从 2 2 2开始算),求 m m m的最小值,没有这样的 m m m输出 − 1 -1 −1。
数据范围
n ≤ 23 n\leq 23 n≤23, 1 ≤ a i ≤ 1 e 9 1\leq a_i\leq 1e9 1≤ai≤1e9
二、解法
这个数据范围很容易联想到状态压缩吧,我们设 d p [ s ] dp[s] dp[s]为我们把当前状态 a i a_i ai一定出现在容器里的为 1 1 1,不一定在容器里的为 0 0 0,那么最终需要状态为 d p [ 2 n ] dp[2^n] dp[2n],也就是 a n a_n an一定在容器里,初始化 d p [ 2 1 ] dp[2^1] dp[21]为 1 1 1,一开始 a 1 a_1 a1一定在容器里(具体实现时需要从 0 0 0开始存)
考虑转移,我们现在需要凑出 i i i,那么我们枚举 i i i之前的两个数( i i i之后的不可能出现),这两个数的和为 i i i,然后我们把当前状态去掉 i i i这一位之后添加上枚举的两个数的位置继续搜索,得到返回值之后我们和当前状态的容器数(也就是 1 1 1的个数)取一个最大值然后更新答案即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 23;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,a[M],dp[1<<M];
int dfs(int x,int p)
{
if(dp[x]) return dp[x];
int c=__builtin_popcount(x),res=25;
for(int i=0;i<p;i++)
for(int j=0;j<=i;j++)
if(a[i]+a[j]==a[p])
{
int t=dfs((x&~(1<<p))|(1<<p-1)|(1<<i)|(1<<j),p-1);
res=min(res,max(t,c));
}
return dp[x]=res;
}
int main()
{
n=read();
for(int i=0;i<n;i++)
a[i]=read();
dp[1]=1;
int t=dfs(1<<(n-1),n-1);
if(t==25) puts("-1");
else printf("%d\n",t);
}