• Subset(POJ-3977)


    题目描述:

     Input:

     Output:

     

     题意:给定n个数,求出使得总和的绝对值最小的最小集合,该集合是给定的n个数的子集,并输出这个最小总和和集合内的元素个数,在满足总和最小的前提下要求元素个数最小。

    思路:这道题如果用暴力来写,由于每个数都有两种情况,故硬枚举就是2^n的效率,但n是小于等于三十五的,这对于指数来说还是太大了。

      本题正解需要用到map,我们借用map来存贮当前情况下每个值下的子集元素个数,用map自带的lower_bound来查找距离指定元素距离最小的值的位置(这两点的具体应用下文会提到)。关于map及lower_bound的详细用法,我向大家推荐这篇博客,这里面有map的实例和用法:

    https://blog.csdn.net/yo_bc/article/details/61195377

      下面来说一下这道题的思路:我们先将这个数组分为两部分,先处理前面的一个部分,利用二进制来表示前半部分的每个元素的出现元素,在这里我们要判断一下,如果这个集合的元素个数为0,就直接continue掉,因为题目中说了集合中元素个数不能为0,在得到相应二进制数值后处理一下,计算一下此时的得分总和及元素个数,之后我们判断一下,如果在map之中相应得分已经有了映射,就对map第二位(即相应得分元素个数)取最小值,没有就直接赋值。之后用相同方法处理另一半,最后我们遍历前面一个数组得出的map,在另一半中找到与map中相应位的第一位总合最小的数(用lower_bound查找),并记录此时的两个部分的元素个数之和。之后取最小值即可。

    注意开long long。

    上代码:

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<map>
     5 typedef long long ll;
     6 using namespace std;
     7 const int N=55;
     8 int n;
     9 ll a[N];
    10 map<ll,int>mp;
    11 bool read(){  //判断是否输入结束 
    12     scanf("%d",&n);
    13     if(!n) return false;
    14     for(int i=0;i<n;++i){
    15         scanf("%lld",&a[i]);
    16     }
    17     mp.clear();
    18     return true;
    19 }
    20 ll Abs(ll a){  //cmath中的abs不针对long long要自己写一个 
    21     return a>0?a:-a;
    22 }
    23 void Solve(){
    24     pair<ll,ll>ans=pair<ll,ll>(Abs(a[0]),1);  //ans记录答案,要先附一个初值,不然滞后没法取min 
    25     int mid=(n>>1);
    26     map<ll,int>::iterator it;  //迭代器,用于查找元素 
    27     int t=(1<<mid);
    28     for(int i=0;i<t;++i){  //处理前半个数组 
    29         ll sum=0;int num=0;
    30         for(int j=0;j<mid;++j)  //统计二进制中1的个数 
    31             if((i>>j)&1){
    32                 sum+=a[j];num++;
    33             }
    34         if(!num) continue;  //没有元素直接continue 
    35         ans=min(ans,pair<ll,ll>(Abs(sum),num)); //计算ans 
    36         it=mp.find(sum);  //查找 
    37         if(it!=mp.end())  //如果原来有取min,原来没有直接赋值 
    38             it->second=min(it->second,num);
    39         else mp[sum]=num;
    40     }
    41     t=(1<<(n-mid));
    42     for(int i=0;i<t;++i){  //后半部分 
    43         ll sum=0;int num=0;
    44         for(int j=0;j<n-mid;++j)
    45             if((i>>j)&1){
    46                 sum+=a[j+mid];++num;
    47             }
    48         if(!num) continue;
    49         ans=min(ans,pair<ll,ll>(Abs(sum),num));
    50         it=mp.lower_bound(-sum);  //查找与sum和最小的值在map中位置 
    51         //在找到的位置左右分别比较,但不能越界 
    52         if(it!=mp.end()) ans=min(ans,pair<ll,ll>(Abs(sum+it->first),it->second+num)); 
    53         if(it!=mp.begin()){
    54             --it;
    55             ans=min(ans,pair<ll,ll>(Abs(sum+it->first),it->second+num));
    56         }
    57     }
    58     printf("%lld %lld
    ",ans.first,ans.second);
    59 }
    60 int main(){
    61     while(read()){
    62         Solve();
    63     }
    64     return 0;
    65 }
    View Code

     

  • 相关阅读:
    整数转字符串
    SharePoint介绍性文章
    Disable Sharepoint 2007 show as System Account when system admin login
    通过IP地址获得主机名
    从文本文件读取信息
    数据库连接池问题[转]
    企业类库问题 public key 问题[经过自己测试]
    Google Analytics异步代码创建虚拟浏览量跟踪
    同一主机上WordPress博客更换域名简易八步骤(2)
    关于application/xwwwformurlencoded等字符编码的解释说明
  • 原文地址:https://www.cnblogs.com/li-jia-hao/p/12772184.html
Copyright © 2020-2023  润新知