题目来源:《高级数据结构》哈希例题
题目描述
小Y得到了两张价值不菲的SHOP购物券,所以他决定去买N件礼物送给朋友们。小Y选好了n件礼物,并且它们的价格之和恰好为两张购物券的面值之和。当小Y被自己的聪明所折服,高兴地去结账时,他突然发现SHOP对购物券的使用有非常奸诈的规定:一次只允许使用一张、不找零、不与现金混用。小Y身上根本没有现金,并且他不愿意放弃挑选好的礼物。这就意味着,他只能通过这两张购物券结账,而且每一张购物券所购买的物品的总价格必须精确地等于这张购物券的面额。怎样才能顺利地买回这n件礼物呢?你的任务就是帮助小Y确实是否存在一个购买方案。小Y会告诉你其中一张购物券的面额以及所有商品的价格,你只需要确定能否找到一种方案使得选出来的物品的价格总和正好是这张购物券的面额即可。
输入
输入有多组数据,每两行有一组数据。每组数据的第一行为两个整数n和m,分别表示小Y一共挑选了n个物品以及小Y的一张购物券的面额为m,接下来的一行有n个用空格隔开的正整数,第i个数表示第i物品的价格。
输出
输出包含若干行,每行一个单词”YES”或者”NO”,分别代表存在一个购买方案和不存在一个购买方案。
数据范围
对于30%的输入文件,所有的n≤20;
对于100%的输入文件,所有的n≤40,并且m和物品的总价值不超过2^31-1,测试组数不超过10组,不少于5组。
题解
当我刚看到这道题时,内心时懵逼的??这跟哈希有什么关系??并且还往背包问题想偏了(捂脸)
首先分析一下题目,每种物品有取和不取的可能,不去重的话,复杂度要2^40,直接爆炸。而且数组不能开到maxint。
嗯,就开始yy。想了很久,终于反应过来,就是取个哈希值,把同余的放在vector的数组里,就不用取到maxint啦!
但是复杂度的问题始终没有解决啊。
事实上可以分成两个部分,分别全排列求和,这样复杂度就是2*2^20,接着枚举合并求解就好了,这样就可以过了!
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
const int H=9973;
using namespace std;
vector<int>ha[H],hb[H];
int n,m,a[44];
bool ptr;
inline void recover()
{
ptr=false;
for(int i=0;i<H;i++){
ha[i].clear();hb[i].clear();
}
}
inline void df(int l,int r,int w)
{
ha[(w+a[l])%H].push_back(w+a[l]);
if(w+a[l]==m || ptr){
ptr=true;return;
}
if(l==r) return;
df(l+1,r,w);
df(l+1,r,w+a[l]);
}
inline void dfs(int l,int r,int w)
{
hb[(w+a[l])%H].push_back(w+a[l]);
if(w+a[l]==m || ptr){
ptr=true;return;
}
if(l==r) return;
dfs(l+1,r,w);
dfs(l+1,r,w+a[l]);
}
inline void find()
{
for(int i=0;i<H;i++){
int len=ha[i].size();
if(len==0) continue;
for(int j=0;j<len;j++){
int q=ha[i][j];
if(m<q) continue;
int x=(m-q)%H;
int lenn=hb[x].size();
if(lenn==0) continue;
for(int kk=0;kk<lenn;kk++){
if((hb[x][kk]+q) == m){
ptr=true;
return;
}
}
}
}
}
int main(){
while(scanf("%d%d",&n,&m)==2){
recover();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int mid=(1+n)>>1;
df(1,mid,0);
if(mid<n) dfs(mid+1,n,0);
if(!ptr)
find();
if(ptr) printf("YES\n");
else printf("NO\n");
}
return 0;
}