洛谷链接:https://www.luogu.org/problemnew/show/T24242
购物券(bday)
【题目描述】
小Y得到了两张价值不菲的SHOP购物券,所以他决定去买N件礼物送给朋友们。小Y选好了n件礼物,并且它们的价格之和恰好为两张购物券的面值之和。当小Y被自己的聪明所折服,高兴地去结账时,他突然发现SHOP对购物券的使用有非常奸诈的规定:一次只允许使用一张、不找零、不与现金混用。小Y身上根本没有现金,并且他不愿意放弃挑选好的礼物。这就意味着,他只能通过这两张购物券结账,而且每一张购物券所购买的物品的总价格必须精确地等于这张购物券的面额。怎样才能顺利地买回这n件礼物呢?你的任务就是帮助小Y确实是否存在一个购买方案。小Y会告诉你其中一张购物券的面额以及所有商品的价格,你只需要确定能否找到一种方案使得选出来的物品的价格总和正好是这张购物券的面额即可。
【输入】
输入有多组数据,每两行有一组数据。每组数据的第一行为两个整数n和m,分别表示小Y一共挑选了n个物品以及小Y的一张购物券的面额为m,接下来的一行有n个用空格隔开的正整数,第i个数表示第i物品的价格。
【输出】
输出包含若干行,每行一个单词”YES”或者”NO”,分别代表存在一个购买方案和不存在一个购买方案。
【输入样例】
10 2000
1000 100 200 300 400 500 700 600 900 800
10 2290
1000 100 200 300 400 500 700 600 900 800
【输出样例】
YES
NO
【提示】
对于30%的输入文件,所有的n≤20;
对于100%的输入文件,所有的n≤40,并且m和物品的总价值不超过 231−1 2 31 − 1 ,测试组数不超过10组,不少于5组。
题解
第一眼看是01背包,再看一眼 231 2 31 的范围,凉。。。orz
所以当然用模拟退火啦然而实测n=20稳过,n=40玄学,再加上多组询问非常爆炸,期望得分30。。。
我们考虑所有的状态,一共 240 2 40 种,但是这道题只要求YES或NO,所以我们只需要暴力枚举前一半物品的状态存到哈希表中,上限 220 2 20 ;再枚举后一半物品的状态,查找满足购物券的另一半状态,上限也是 220 2 20 ,期望得分100。。。
我们使用dfs枚举所有状态,到达上界时插入or查找。
代码
#include<bits/stdc++.h>
using namespace std;
const int mod=100003;
int n,m,x[45];
bool f;
vector<int>hash[mod+5];
void in()
{
for(int i=1;i<=n;++i)
scanf("%d",&x[i]);
}
void ins(int k)
{
int nn=k%mod;
for(int i=hash[nn].size()-1;i>=0;--i)
if(hash[nn][i]==k)return;
hash[nn].push_back(k);
}
void dfs1(int v,int sum)
{
if(sum==m)
{f=1;return;}
if(v>(n>>1))
{ins(sum);return;}
if(sum>m)return;
dfs1(v+1,sum+x[v]);
dfs1(v+1,sum);
}
bool check(int k)
{
int nn=k%mod;
for(int i=hash[nn].size()-1;i>=0;--i)
if(hash[nn][i]==k)return 1;
return 0;
}
void dfs2(int v,int sum)
{
if(f)return;
if(sum>m)return;
if(v>n)
{if(check(m-sum))f=1;return;}
dfs2(v+1,sum+x[v]);
dfs2(v+1,sum);
}
void ac()
{
dfs1(1,0);
dfs2(n>>1|1,0);
if(f)printf("YES\n");
else printf("NO\n");
}
void reset()
{
f=0;
for(int i=0;i<mod;++i)
hash[i].clear();
memset(x,0,sizeof(x));
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
reset(),in(),ac();
return 0;
}