【2018/11/01搜索大赛T2】某种密码

【题目】

传送门


【分析】

0 pts:

这道题可以理解为一个 01 01 01 背包问题的变种, n n n 为物品个数,原文为物品的重量,钥匙码为背包容量,求恰好装满背包的方案数。

如果是一道普通 01 01 01 背包求恰好装满背包的方案数的题,可以用 d p dp dp 求解。具体思路是设 f i , j f_{i,j} fi,j 为用前 i i i 个物品恰好装满容量为 j j j 的背包的方案数,转移方程为 f i , j = f i − 1 , j + f i − 1 , j − w i f_{i,j}=f_{i−1,j}+f_{i−1,j−w_i} fi,j=fi1,j+fi1,jwi。显然可以滚动数组或一维数组优化,在此不再赘述。

然而,观察数据范围会发现,此处物品的重量极大,达到了 i n t int int 的范围,因此 d p dp dp 的时空复杂度均无法承受,然而 n n n 的范围较小,可以考虑搜索。

60 pts:

直接暴搜,O ( 2 n ) (2^n) (2n) 枚举放入背包的物品集合,将重量相加并与 k e y key key 比较。

这应该是比较好想又比较好写的,在考场上我也写了这种。

然而,对于剩余的 40 % 40\% 40% 数据,复杂度太大,仍然无法解决。

100 pts:

其实这是一类经典问题,即从一个集合中选取某个子集,使其中所有元素值之和等于某个给定值。

那么,这是折半搜索就闪亮登场了

将物品集合均分成两个交集为空,补集为全集的集合 A A A B B B,对集合 A A A 暴力枚举其所有子集中元素和并存入哈希表中(当然可以用 m a p map map),再对集合 B B B 暴力枚举每个子集的元素和 s s s,同时查找哈希表中值为 ( k e y − s ) (key−s) (keys) 的元素个数并计数。时间复杂度为O ( 2 ∗ 2 n 2 ) (2∗2^{\frac{n}2}) (222n),也就是 O ( 2 n 2 ) (2^\frac{n}2) (22n),可以接受。

扩展

此外,还有一类 01 01 01 背包的变种。这类问题中物品个数与普通 01 01 01 背包类似,物品重量与背包容量极大,但物品单个价值较小,求装入背包的物品价值的最大值。

此类问题可以用 d p dp dp 求解,只是需要对状态进行些许修改。设 f i , j f_{i,j} fi,j 为前 i i i 个物品价值总量为 j j j 时所需的背包容量的最小值,状态转移方程为 f i , j = m i n ( f i − 1 , j , f i − 1 , j − c i + w i ) f_{i,j}=min(f_{i−1,j},f_{i−1,j−c_i}+w_i) fi,j=min(fi1,j,fi1,jci+wi)。最后扫描 f n , i f_{n,i} fn,i,找到最接近给定背包容量的 i i i 输出即可。


【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<tr1/unordered_map>
#define N 105
using namespace std;
using namespace tr1;
int n,key,A[N];
long long ans=0;
unordered_map<int,int>Map;
void dfs(int now,int end,int sum)
{
	if(now>end)
	{
		Map[sum]++;
		return;
	}
	dfs(now+1,end,sum);
	dfs(now+1,end,sum+A[now]);
}
void solve(int now,int end,int sum)
{
	if(now>end)
	{
		ans+=Map[key-sum];
		return;
	}
	solve(now+1,end,sum);
	solve(now+1,end,sum+A[now]);
}
int main()
{
//	freopen("password.in","r",stdin);
//	freopen("password.out","w",stdout);
	int i;
	scanf("%d%d",&n,&key);
	for(i=1;i<=n;++i)
	  scanf("%d",&A[i]);
	dfs(1,(1+n)/2,0);
	solve((1+n)/2+1,n,0);
	printf("%lld",ans);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值