题目链接
题意:在一个集合中找到一个非空子集使得这个子集元素和的绝对值尽量小,和绝对值相同时保证元素个数尽量小
1.折半枚举,先分成两部分,第一部分用二进制枚举所有情况,并记录在数组中,数组中应该记录两个元素,一个是每种情况的 元素之和,另一个是有多少个元素相加。
2. 记录完之后,排序,元素和一样的,更新第二个数据(有多少元素相加),取最少的元素。
3. 之后再枚举另一半,在记录中的数组中找到合适的值使他们的和最小,
怎么找的呢,用lower_bound(), 来找, 找到的是>= 的值,从上往下循环。
我们要找的是最接近,所以有可能是大于,或者小于。
如果找到的值正好等于,那就太好了,如果不是,那就向下循环,再找一个比要找的值小一点的。
要注意边界情况。
有bug 的是,,abs 不能使用long long ,所以要自己写一个 函数。
自己做这个题的时候,花了一个下午,还是没有改出来,最后还是看网上的代码。
注意几点
pair 比较的时候 应该是比较 两个值,先比较第一个,然后比较第二个。
abs 函数不能 用于 longlong
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
#define LL long long
#define X first
#define Y second
#define N 40
const int M = 3e5;
typedef pair<LL,int>P;
int n,m;
LL a[N];
P ps[M],ans;
LL ABS(LL x){
return x > 0 ? x : -x;
}
void check(LL now, int tot){
int t = lower_bound(ps,ps + (1 << m),P(-now,0))-ps;
for (int i = min(t,(1 << m)-1); i >= max(t-1,0); i--)
ans = min(ans,P(ABS(now+ps[i].X),tot+ps[i].Y));
return;
}
int main() {
while(scanf("%d",&n) == 1 && n > 0) {
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
}
m = n / 2;
ans = P(ABS(a[0]),1);
for (int i = 0; i < (1 << m); i++) {
ps[i].Y = 0;
ps[i].X = 0;
for (int j = 0; j < m; j++) {
if ((i >> j) & 1) {
ps[i].X += a[j];
ps[i].Y++;
}
}
if (i) ans = min(ans, P(ABS(ps[i].X), ps[i].Y)); // 枚举一半,一边枚举,一边比较。把第一半的值枚举一遍,比较一遍。然后枚举下一半的时候,就不需要从0开始。从0开始的话,check中会有bug,就需要在加一些代码来完善了。
}
sort(ps, ps + (1 << m));
for (int i = 0; i < (1 << m) - 1; i++)
if (ps[i].X == ps[i + 1].X)
ps[i + 1].Y = ps[i].Y;
for (int i = 1; i < (1 << n - m); i++) {
LL sw = 0;
int sv = 0;
for (int j = 0; j < (n - m); j++) {
if ((i >> j) & 1) {
sw += a[m + j];
sv++;
}
}
check(sw,sv);
}
//printf("%lld %d\n",ans.X,ans.Y);
cout<<ans.X<<" "<<ans.Y<<endl;
}
return 0;
}
附上自己的错误代码。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
#define mem(x,v) memset(x,v,sizeof(x))
const int N = 40;
int n;
long long a[N];
pair<long long, int >ps[1 << 18];
long long ABS(long long x){
return x > 0 ? x : -x;
}
int main() {
int m;
while(scanf("%d",&n) == 1 && n > 0){
bool flag = 0;
long long z = 1e16,ans;
int tot;
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
z = min(z,ABS(a[i]));
if (a[i] < 0) flag = 1;
}
ans = z; tot = 1;
if (n == 1 || flag == 0){
// printf("%lld %d\n",ans,tot);
cout<<ans<<" "<<tot<<endl;
continue;
}
m = n / 2;
mem(ps,0);
for (int i = 1; i < 1 << m; i++){
long long sw = 0;
int sv = 0;
for (int j = 0; j < m; j++){
if (i >> j & 1){
sw += a[j];
sv++;
}
}
ps[i] = make_pair(sw,sv);
}
// printf("%d\n",tot);
sort(ps + 1,ps+(1 << m));
// for (int i = 1; i < (1 << m); i++)
// printf("%d 55 %d\n",ps[i].first,ps[i].second);
int k;
for (int i = 1; i < (1 << n-m); i++) {
long long sw = 0;
int sv = 0;
for (int j = 0; j < (n - m); j++) {
if (i >> j & 1) {
sw += a[m + j];
sv++;
}
}
// printf("%lld %d \n", sw, sv);
if (sw != 0) sw = -sw;
int s = lower_bound(ps + 1, ps + (1 << m), make_pair(sw, -1)) - ps;
int t = upper_bound(ps + 1, ps + (1 << m), make_pair(sw, 2*m)) - ps;
// cout<<s<<" "<<t<<endl;
if (s == t){
if (t != (1 << m)){
if (ABS(ps[t].first - sw) <= ans && (sv + ps[t].second) > 0){
if (ABS(ps[t].first - sw) < ans){
ans = ABS(ps[t].first - sw);
tot = ps[t].second + sv;
} else tot = min(tot, ps[t].second);
}
k = t+1;
while(k < (1 << m) && ps[k].first == ps[k-1].first){
tot = min(tot,ps[k].second+sv);
k++;
}
}
k = s-1;
if (k > 0){
if (ABS(ps[k].first - sw) <= ans && (sv + ps[k].second) > 0){
if (ABS(ps[k].first - sw) < ans){
ans = ABS(ps[k].first - sw);
tot = ps[k].second + sv;
} else tot = min(tot, ps[k].second);
}
k--;
while(k > 0 && ps[k].first == ps[k+1].first){
tot = min(tot,ps[k].second+sv);
k--;
}
}
}
if (s != t){
ans = 0;
tot = ps[s].second + sv;
for (int j = s + 1 ; j < t; j++)
tot = min(tot,ps[j].second+sv);
}
}
cout<<ans<<" "<<tot<<endl;
// printf("%lld %d\n",ans,tot);
}
return 0;
}