POJ3977 Subset

Posted captain1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ3977 Subset相关的知识,希望对你有一定的参考价值。

传送门

题目大意:给定一个含有n个元素的集合,求出其中一个子集,使得在子集内所有元素的和的绝对值最小的情况下,子集元素个数最小。输出最小值和最少元素个数。n <= 35.

这个题是很显然的折半搜索,我们先搜前一半,再搜后一半,搜到后一半的时候,我们在前一半元素之中二分去找最接近0的那一个值来更新答案。

思路很清晰但是坑特别多……

首先是,如果你采用我这种算法,你需要特判掉输入中有0的情况。否则的话会出现一些奇怪的问题……

还有就是需要判重。因为我们是二分一个值之后还要找其前驱后继。后继没问题,但是如果有多个前驱的值是一样的而选取的元素不一样,那么是不行的,因为我们会取到元素最多的那一个。方法就是在sort之后判重,只取值相同的那些中元素最少的一个。

还有就是,我们在搜前一半的时候需要更新答案。(这个忘了让我调了好久……)

本题拍了几千组数据也没问题……但是想不到在这翻车。

mrclr的代码有误都能过orzzzzzz

之后看一下代码吧……与二进制相比唯一优秀的可能是比较清真。

技术分享图片
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘
‘)
#define fr friend inline
#define y1 poj

using namespace std;
typedef long long ll;
const int M = 1000005;
const ll INF = 1e18;
const double eps = 1e-7;

#define pr pair<ll,int>
#define mp make_pair
#define fi first
#define sc second

ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
    if(ch == -) op = -1;
    ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
    ans *= 10;
    ans += ch - 0;
    ch = getchar();
    }
    return ans * op;
}

ll n,a[105],cnt,ans,minn,k,tot;
pr num[M],res[M];

ll Abs(ll x)
{
   return x > 0 ? x : -x;
}

bool cmp(pr p, pr q)
{
   if(p.fi == q.fi) return p.sc < q.sc;
   return p.fi < q.fi;
}

void dfs(int step,ll sum,ll take)
{
   if(step > k)
   {
      if(take != 0)
      {
     ll f = Abs(sum);
     if(f < minn || (f == minn && take < ans)) minn = f,ans = take;
      }
      num[++cnt].fi = sum,num[cnt].sc = take;
      return;
   }
   dfs(step+1,sum,take),dfs(step+1,sum+a[step],take+1);
}

void judge(int p,ll c,ll t)
{
   if(p > tot || p < 1) return;
   ll cur = Abs(res[p].fi + c),now = res[p].sc + t;
   if(!now) return;
   if(cur < minn || (cur == minn && now < ans)) minn = cur,ans = now;
}

void dfs2(int step,ll sum,ll take)
{
   if(step == k)
   {
      int g = lower_bound(res+1,res+1+tot,mp(-sum,0),cmp) - res;
      judge(g,sum,take),judge(g+1,sum,take),judge(g-1,sum,take);
      return;
   }
   dfs2(step-1,sum,take),dfs2(step-1,sum+a[step],take+1);
}

int main()
{
   while(1)
   {
      n = read(),k = n >> 1;if(!n) break;
      memset(num,0,sizeof(num));
      memset(res,0,sizeof(res));
      memset(a,0,sizeof(a));
      bool flag = 0;
      rep(i,1,n)
      {
     a[i] = read();
     if(a[i] == 0) flag = 1;
      }
      if(flag) {printf("0 1
");continue;}
      minn = INF,ans = a[0],cnt = tot = 0;
      dfs(1,0,0),sort(num+1,num+1+cnt,cmp);
      res[++tot] = num[1];
      rep(i,2,cnt) if(num[i].fi != num[i-1].fi) res[++tot] = num[i];
      dfs2(n,0,0);
      printf("%lld %lld
",minn,ans);
   }
   return 0;
}
View Code

 

以上是关于POJ3977 Subset的主要内容,如果未能解决你的问题,请参考以下文章

POJ3977:Subset——题解(三分+折半搜索)

poj3977 - subset - the second time - 暴力 + 二分

POJ3977 Subset

Subset(POJ-3977)

POJ 3977 Subset | 折半搜索

POJ - 3977 Subset(二分+折半枚举)