由孟神纠正出的一个错别字,现已更正。
特别提示:膜孟神能有特殊BUFF加成。
梁神
园神
译自 CEOI2015 Day2 T1「Ice Hockey World Championship」
今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。
给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。
第一行,两个正整数 N 和 M ( 1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 ) M(1 \leq N \leq 40,1 \leq M \leq 10^{18}) M(1≤N≤40,1≤M≤1018),表示比赛的个数和 Bobek 那家徒四壁的财产。
第二行,N 个以空格分隔的正整数,均不超过 1 0 16 10^{16} 1016 ,代表每场比赛门票的价格。
输出一行,表示方案的个数。由于 NN 十分大,注意:答案 ≤ 2 40 \le 2^{40} ≤240。
5 1000
100 1500 500 500 1000
8
样例解释
八种方案分别是:
一场都不看,溜了溜了
价格100的比赛
第一场价格500的比赛
第二场价格500的比赛
价格 100 的比赛和第一场价格 500的比赛
价格 100 的比赛和第二场价格 500的比赛
两场价格 500的比赛
价格 1000 的比赛
有十组数据,每通过一组数据你可以获得 10 分。各组数据的数据范围如下表所示:
数据组号 1-21−2 3-43−4 5-75−7 8-108−10
N ≤ N \leq N≤ 10 | 20 | 40 |40
M ≤ 1 0 6 ∣ 1 0 18 ∣ 1 0 6 ∣ 1 0 18 M \leq10^6|10^{18} | 10^6|10^{18} M≤106∣1018∣106∣1018
这题在不是很相信洛谷上是紫题,毕竟很快就想出来了。
当然还是得益于大佬提点我的那句:爆搜。
机房有一大佬,一日与不知何处的小学弟QQ上交流,学弟给出一题:现有一背包,背包有一承重W
有n个物品,物品有重量 v i v_i vi,求装背包的方案数 。
N ≤ 40 , M ≤ 1 0 18 N\leq40, M\leq10^{18} N≤40,M≤1018
对,就是这道题改了下描述。
当日,大佬说题难度普及,要我与其同思其解,帮学弟解疑答惑。
我信了题目的鬼,深以为是背包dp,思良久不得解。
忽另一大佬路过,听其题意后,戏笑而说:爆搜。
吾思之,N若为20,尚可枚举,今有40,不可枚举。
忽灵光一闪至,为何不从两边搜,一边搜20,复杂度岂不可过?
搜两次,统计方法答案为何?
可若第二次搜完时枚举第一次状态判断合法否,复杂度仍为 2 40 2^{40} 240,吾休矣。
这时大佬突然对我施以援手:记录第一次搜的状态,排序,二分查找。
感激甚,大佬无愧于大佬,救人于水深火热之中,如菩萨降临,带众生脱离苦海。
好,
第一次搜时用a数组存下合法状态,即每次搜完后合法的门票价钱和。
对存下的状态进行递增排序
第二次时,对于每一个搜完的合法状态,我们记选择的门票为 b b b;前一次搜出的状态为 a i a_i ai
那么和并状态时,合并合法的条件是 a i + b ≤ m a_i + b \leq m ai+b≤m 变一下就是 a i ≤ m − b a_i \leq m - b ai≤m−b
我们刚刚对a数组进行了排序,那么我们就可以找到第一个大于 m − b m-b m−b的数,这个数前面的数都是合法的,
那么合法的就有改数下标-1个,直接加到答案中去即可。
这样的话我们的复杂度就是 O ( 2 n 2 + 2 n 2 l o g ( 2 n 2 ) ) O(2^{\frac{n}{2}} + 2^{\frac{n}{2}} log(2^{\frac{n}{2}})) O(22n+22nlog(22n))
此题可过矣。
C o d e Code Code
#include
#define ll long long
#define MAXN 500010
#define N 201
#define INF 0x3f3f3f3f
#define gtc() getchar()
using namespace std;
template <class T>
inline void read(T &s){
s = 0; T w = 1, ch = gtc();
while(!isdigit(ch)){if(ch == '-') w = -1; ch = gtc();}
while(isdigit(ch)){s = s * 10 + ch - '0'; ch = gtc();}
s *= w;
}
template <class T>
inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x/10);
putchar(x % 10 + '0');
}
int n;
ll m;
ll w[MAXN];
ll a[1 << 21], cnt = 0;
int mid;
void dfs(int x, ll ans){//枚举前一半
if(ans > m) return ;//剪枝
if(x > mid){
a[++cnt] = ans;
return ;
}
// printf("%d %d\n", x , ans);
dfs(x+1, ans);
dfs(x+1, ans + w[x]);
}
ll ans = 0;
void dfs2(int x, ll num){//枚举后一半
if(num > m) return ;//剪枝
if(x > n){//搜完后二分查找
int tx = upper_bound(a+1, a+cnt+1, m - num) - a - 1;
ans += tx;//累加答案
return ;
}
dfs2(x+1, num);
dfs2(x+1, num+w[x]);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
read(n), read(m);
mid = n >> 1;
for(int i = 1; i <= n; ++i) read(w[i]);
dfs(1, 0);
sort(a + 1, a + cnt + 1);
dfs2(mid + 1, 0);
cout << ans << endl;
return 0;
}