【POJ 3977】【折半枚举】【超大背包】Subset【暑期 No.7】

题意:

        给一串数字,选取其中的数字,使其值最小,并且选取数字的个数尽可能少.

分析:

        因为数字范围很大,因此不能使用动态规划来计算,从而考虑折半枚举来进行计算。

        将这串数字划分为两部分,第一部分用字典序枚举的方式输出所有的可能结果,将结果输入数组,再对数组进行升序。

       再对该串数字的另一部分,进行字典序枚举,对于每一种可能性,在第一个数组中对这种可能性的负数进行二分查找,并与ans进行比较。

【本题在各种区间细节中有些麻烦,需要注意,详情见代码】

代码:

#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
typedef long long ll;
const ll SIZE = 1<<18;

int n,ans_num;
ll a[40];
ll ans;

struct TMP1{
  ll v;
  int id;
}tmp1[SIZE];

bool operator < (TMP1 x,TMP1 y){
  if(x.v!=y.v)
    return x.v<y.v;
  else
    return x.id<y.id;
}

ll abss(ll x){
  if(x < 0) x = (-1)*x;
  return x;
}

int main()
{
  while(~scanf("%d",&n),n)
  {
    rep(i,1,n){
      scanf("%lld",&a[i]);
    }
    ans_num = 0;
    ans = 1e17;
    int k = 0;
    //用字典序枚举出1-n/2物品的所有取用方式
    rep(i,1,(1<<(n/2))-1){
      ll tmpv = 0;
      int tmpn = 0;
      rep(j,0,n/2-1){
        int x = (i>>j)&1;
        if(x){
            tmpv += a[n/2 - j];
            tmpn++;
        }
      }
      ++k;
      tmp1[k].v = tmpv;
      tmp1[k].id = tmpn;
      if(ans > abss(tmpv)){
        ans = abss(tmpv);
        ans_num = tmpn;
      }
      else if(ans == abss(tmpv) && ans_num > tmpn) ans_num = tmpn;
    }
    //删去无用元素
    sort(tmp1+1,tmp1+1+k);
    int m1 = 1;
    rep(i,2,k){
      if(tmp1[m1].v != tmp1[i].v){
      	++m1;
	    tmp1[m1].v = tmp1[i].v;
	    tmp1[m1].id = tmp1[i].id;
	  }
    }
    //枚举另外一边的元素
    rep(i,1,(1<<(n-n/2))-1){
      ll tmpv = 0;
      int tmpn = 0;
      rep(j,0,n-n/2-1){
        int x = (i>>j)&1;
        if(x){
            tmpv += a[j+n/2+1];
            tmpn++;
        }
      }
      TMP1 tp;
		  tp.v = (-1)*tmpv;
		  tp.id = tmpn;
      if(ans > abss(tmpv)){
        ans = abss(tmpv);
        ans_num = tmpn;
      }
      else if(ans == abss(tmpv) && ans_num > tmpn) ans_num = tmpn;
      int kk1 = lower_bound(tmp1+1,tmp1+m1+1,tp)-tmp1;
      if(kk1 <= 0) kk1 = 1;
      if(kk1 > m1) kk1 = m1;
      if(abss(tmpv+tmp1[kk1].v)<ans)
      {
        ans = abss(tmpv+tmp1[kk1].v);
        ans_num = tmpn+tmp1[kk1].id;
      }
      else if(abss(tmpv+tmp1[kk1].v) == ans && (tmpn+tmp1[kk1].id) < ans_num)
        ans_num = tmpn+tmp1[kk1].id;
      //有一点是debug长达3h的关键错误
      //lower_bound是返回 >= 的值
      //upper_bound是返回 > 的值
      //千万不要以为lower_bound是返回 <= !!!!!!!!!
      int kk2;
      if(kk1 > 1) kk2 = kk1-1;
      else continue;
      if(abss(tmpv+tmp1[kk2].v)<ans)
      {
        ans = abss(tmpv+tmp1[kk2].v);
        ans_num = tmpn+tmp1[kk2].id;
      }
      else if(abss(tmpv+tmp1[kk2].v) == ans && (tmpn+tmp1[kk2].id) < ans_num)
        ans_num = tmpn+tmp1[kk2].id;
    }
    printf("%lld %d\n",ans,ans_num);
}
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/81255464