前言
本来是在做搜索剪枝题单的倒数第二题,发现不会做,看了看题解里大佬说这一题更容易,于是打算先从简单入手。
自己YY了个做法,推了推复杂度,好像能过,码了码交上去WA一片,仔细观察了一下发现是二分之前忘记排序了……排序后交上去果然过了。
AC之后片刻,突然意识到自己像个sb:为什么不用upper_bound()
呢?
题目描述
题目链接:洛谷P4799 世界冰球锦标赛.
大意是,有 n n n个数,还有一个数 m m m,求 n n n个数任选若干数的和不超过 m m m的方案数有多少。
例如:
n = 5, m = 1000
a[] = 100 1500 500 500 1000
一共有 8 8 8种方案。
题目分析
观察到 n n n只有40,于是我们先考虑暴力。
枚举每一个数 选 or 不选,在dfs的过程中记录当前已选数的和,最后判断是否小于 m m m,复杂度 O ( 2 n ) O(2^n) O(2n).
可是这个 n n n有40啊,要是能让 n n n减半该多好。
于是这么思考了起来:将 n n n个数分成前 n / 2 n/2 n/2个数与后 n / 2 n/2 n/2个数两部分,对前半部分枚举每一个数 选 or 不选,每种方案所选数的和放入 s 1 [ ] s1[] s1[];对后半部分进行同样的操作,将每种方案所选数的和存入 s 2 [ ] s2[] s2[]。对于 s 1 [ ] s1[] s1[]中的一个和 a a a, s 2 [ ] s2[] s2[]中所有小于等于 m − a m-a m−a的方案都是可行的。
于是只需要对枚举 s 1 [ ] s1[] s1[]中的每一个 a a a,计算 s 2 [ ] s2[] s2[]中小于等于 m − a m-a m−a的数有多少个,那么答案就加多少。这里可以对 s 2 [ ] s2[] s2[]排序之后,进行二分查找,总复杂度 O ( 2 n 2 l o g ( 2 n 2 ) ) = O ( n 2 n 2 ) O(2^\frac{n}{2}log(2^\frac{n}{2}))=O(n2^\frac{n}{2}) O(22nlog(22n))=O(n22n),可以过掉。
我忘了有个upper_bound()
于是手写的二分,不得不说要注意的细节还是有一些的。
示例代码
- 手写二分:
vector<int> s[2]; //s[0]与s[1]分别对应上文说的s1与s2
int bi_search(LL v){
int l, r, mid;
l = 0; r = (int)s[1].size()-1;
if(r == -1) return 0; //这句不要也行,可以想想为什么
while(l < r){ // 最后l与r指向第一个大于v的数
mid = (l+r) >> 1;
if(s[1][mid] <= v)
l = mid+1;
else r = mid;
}
if(l==(int)s[1].size()-1 && v>=s[1][l])//特判v比最大值的情况
++ l;
return l;
}
upper_bound()
二分 (秒杀)
int pos = upper_bound(s[1].begin(), s[1].end(), m-s[0][i]) - s[1].begin() //太简单了点hhh
- 完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, ed[2]; //ed[0]:前半段的结束位置 ed[1]:后半段的结束位置
LL m, Ans;
LL a[45];
vector<LL> s[2];// 文中的s1、s2
void dfs(int now, int tp, LL sum){//now:当前为a[now], tp:0:前半段/1:后半段, sum:当前已选数的和
if(sum > m) return; //小剪枝
if(now > ed[tp]){ // 枚举完这半段
s[tp].push_back(sum);
return;
}
dfs(now+1, tp, sum+a[now]); //选
dfs(now+1, tp, sum); //不选
}
int main(){
cin >> n >> m;
for(int i=1; i<=n; ++i)
cin >> a[i];
ed[0] = n/2; ed[1] = n; //前半段和后半段的结束位置分别为n/2 与 n
dfs(1, 0, 0); dfs(n/2+1, 1, 0);
sort(s[1].begin(), s[1].end()); // 二分前千万不要忘了排序!
int l = s[0].size();
for(int i=0; i<l; ++i){
int pos = upper_bound(s[1].begin(), s[1].end(), m-s[0][i]) - s[1].begin(); // 二分
Ans += pos;
}
printf("%lld", Ans);
return 0;
}
后记
二分前忘记排序,忘记upper_bound()
这些小失误依旧存在,不过能够明显感觉自己码力变强了不少,以前碰到这样的题可能思考都不会思考,写个暴力就走人了,现在能够进一步的有新的想法,还是对自己很有期待的。
继续加油!