Subset POJ - 3977(折半枚举+二分查找)
题目描述
Given a list of N integers with absolute values no larger than 10 15, find a non empty subset of these numbers which minimizes the absolute value of the sum of its elements. In case there are multiple subsets, choose the one with fewer elements.
输出
The input contains multiple data sets, the first line of each data set contains N <= 35, the number of elements, the next line contains N numbers no larger than 10 15 in absolute value and separated by a single space. The input is terminated with N = 0
输入
For each data set in the input print two integers, the minimum absolute sum and the number of elements in the optimal subset.
样例
样例输入
1
10
3
20 100 -100
0
样例输出
10 1
0 2
分析
一句话题意:多组数据,以 0 为结尾,给你n个数,求出这n个数的一个非空子集,使子集中的数加和的绝对值最小,在此基础上子集中元素的个数应最小。
N的最大值为35,暴力枚举肯定是要超时的,因为你要把235种情况都枚举出来,即使30秒的时限也是不够用的
暴力枚举
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=50;
ll a[maxn],ans;
int n,cnt;
ll mabs(ll x){
if(x>0) return x;
return -x;
}
void dfs(int now,ll tot,int xuan){
if(xuan && mabs(tot)<ans){
cnt=xuan;
ans=mabs(tot);
}
else if(xuan && ans==mabs(tot)) cnt=min(cnt,xuan);
for(int i=now;i<=n;i++){
dfs(i+1,tot+a[i],xuan+1);
}
}
int main(){
while(scanf("%d",&n)!=EOF && n!=0){
ans=0x3f3f3f3f3f3f3f3f;
cnt=40;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
dfs(1,0,0);
printf("%lld %d\n",ans,cnt);
}
return 0;
}
其实我们可以考虑把n个数分成两部分,每个部分最多有218种结果
我们先把前一半预处理处理出来,然后再枚举后一半
对于每一个值,我们都要从前一半中找到与它的相反数最接近的数,然后两数相加取最小值
需要注意的是,最优解也有可能出现在前一半或后一半,不一定是前一半中的数与后一半中的数相加的结果
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int maxn=50;
int n,cnt=0;
map<ll,ll> ma;
//map记录当前值所选的最少元素个数
ll a[maxn],jl[1<<20],ans=1e18;
//jl数组存储前一半的结果,ans是最终结果
ll abss(ll x){
if(x>0) return x;
return -x;
}//绝对值函数
ll solve(ll num){
if(num<=jl[1]) return jl[1];
else if(num>=jl[cnt]) return jl[cnt];
else {
ll l=1,r=cnt,mids;
while(l<=r){
mids=(l+r)/2;
if(jl[mids]==num) break;
else if(jl[mids]<num) l=mids+1;
else r=mids-1;
}
if(jl[mids]==num){
return num;
}
else if(abss(jl[l]-num)==abss(jl[r]-num)){
if(ma[jl[l]]<ma[jl[r]]) return jl[l];
return jl[r];
}
else if(abss(jl[l]-num)>=abss(jl[r]-num)){
return jl[r];
} else {
return jl[l];
}
}
}//模板,查找与x最接近的元素,要注意的是如果有多个元素与x最接近,取数的个数最小的那一个
int main(){
while(scanf("%d",&n)!=EOF && n!=0){
cnt=0,ans=1e18;
ma.clear();
memset(a,0,sizeof(a));
memset(jl,0,sizeof(jl));
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}//初始化+输入
if(n==1){
printf("%lld 1\n",abss(a[1]));
continue;
}//如果只有一个数,特判一下,因为前一半没有数,无法二分
int le=n/2,re=n-le;
int mmax=(1<<le)-1;
ll id=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=mmax;i++){
ll now=0,js=0;
for(int j=1;j<=le;j++){
if(i&(1<<(j-1))) now+=a[j],js++;
}
if(ma[now]) ma[now]=min(ma[now],js);
//如果当前值已经出现过,元素个数取较小的
else ma[now]=js,jl[++cnt]=now;
//没有出现过,建立映射关系
if(abss(now)<ans){
ans=abss(now);
id=js;
}
//如果答案更优,更新答案
else if(abss(now)==ans){
id=min(id,js);
}
//如果答案相同,元素个数取较小的
}
//枚举前一半数,同时记录答案
sort(jl+1,jl+cnt+1);
//排序
for(int i=1;i<(1<<re);i++){
ll now=0,js=0;
for(int j=1;j<=re;j++){
if(i&(1<<(j-1))){
now+=a[j+n/2],js++;
}
}
if(abss(now)<ans){
ans=abss(now);
id=js;
}
else if(abss(now)==ans){
id=min(id,js);
}
//同上
ll aft=solve(-now);
//二分找到与相反数最接近的数
if(ans>abss(aft+now)){
ans=abss(aft+now);
id=js+ma[aft];
}//如果答案更优,更新答案
else if(ans==abss(aft+now)) id=min(id,js+ma[aft]);
//如果和之前的答案相同,个数取较小的
}
printf("%lld %d\n",ans,id);
}
return 0;
}
Subset POJ - 3977(折半枚举+二分查找)的更多相关文章
- CSU OJ PID=1514: Packs 超大背包问题,折半枚举+二分查找。
1514: Packs Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 61 Solved: 4[Submit][Status][Web Board] ...
- poj3977(折半枚举+二分查找)
题目链接:https://vjudge.net/problem/POJ-3977 题意:给一个大小<=35的集合,找一个非空子集合,使得子集合元素和的绝对值最小,如果有多个这样的集合,找元素个数 ...
- Subset---poj3977(折半枚举+二分查找)
题目链接:http://poj.org/problem?id=3977 给你n个数,找到一个子集,使得这个子集的和的绝对值是最小的,如果有多种情况,输出子集个数最少的: n<=35,|a[i]| ...
- POJ 3977 折半枚举
链接: http://poj.org/problem?id=3977 题意: 给你n个数,n最大35,让你从中选几个数,不能选0个,使它们和的绝对值最小,如果有一样的,取个数最小的 思路: 子集个数共 ...
- POJ 3273 Monthly Expense二分查找[最小化最大值问题]
POJ 3273 Monthly Expense二分查找(最大值最小化问题) 题目:Monthly Expense Description Farmer John is an astounding a ...
- 【折半枚举+二分】POJ 3977 Subset
题目内容 Vjudge链接 给你\(n\)个数,求出这\(n\)个数的一个非空子集,使子集中的数加和的绝对值最小,在此基础上子集中元素的个数应最小. 输入格式 输入含多组数据,每组数据有两行,第一行是 ...
- POJ 3977 Subset(折半枚举+二分)
SubsetTime Limit: 30000MS Memory Limit: 65536KTotal Submissions: 6754 Accepted: 1277 D ...
- POJ 2549 Sumsets(折半枚举+二分)
Sumsets Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 11946 Accepted: 3299 Descript ...
- Divide and conquer:Subset(POJ 3977)
子序列 题目大意:给定一串数字序列,要你从中挑一定个数的数字使这些数字和绝对值最小,求出最小组合数 题目的数字最多35个,一看就是要数字枚举了,但是如果直接枚举,复杂度就是O(2^35)了,显然行不通 ...
随机推荐
- “造轮运动”之 ORM框架系列(三)~ 干货呈上
这一趴里面,我就来正式介绍一下CoffeeSQL的干货. 首先要给CoffeeSQL来个定位:最开始就是由于本人想要了解ORM框架内部的原理,所以就四处搜寻有关的博客与学习资料,就是在那个夏天 ...
- bfs算法总结
BFS(广度优先搜索) 类似于树的按层遍历 常用实现方法:队列 模板: 注意: 只能用来求解无权图的最短路径问题 队列:用来存储每一层便利得到的节点 标记:对于遍历过的结点,应将其标记,以防重复访问 ...
- ubuntu12.04 empathy添加qq登陆
1,
- 使用PD(Power Designer)设计数据库,并且生成可执行的SQL文件创建数据库(本文以SQL Server Management Studio软件执行为例)
下载和安装PD: 分享我的软件资源,里面包含了对PD汉化包(链接出问题时可以留言,汉化包只能对软件里面部分菜单栏汉化) 链接:https://pan.baidu.com/s/1lNt1UGZhtDV8 ...
- f(t) = t的傅里叶系数
计算机网络课程讲到物理层,布置作业的第一题是求f(t)=t (0≤t≤1)的傅里叶系数. 我们知道任何一个周期函数都可以被傅里叶级数逼近.如果是实值函数,则可以用正弦分量,余弦分量,直流分量来近似.公 ...
- Day7-微信小程序实战-天气预报小程序
前段时间在B站跟着一个视频,搞天气预报小程序 https://www.bilibili.com/video/BV1cJ411879s 但是因为这个调用的接口要money,太贵了就没买,就只是做了一些不 ...
- RocketMQ 内存优化
rocketmq官方文档 RocketMQ 的默认内存占用非常高,调整RocketMQ的内存目前我所知道的有两个方面: MQ的启动脚本可以调整内存mqbroker和mqnamesrv的配置可以调整内存 ...
- 最通俗易懂的RSA加密解密指导
前言 RSA加密算法是一种非对称加密算法,简单来说,就是加密时使用一个钥匙,解密时使用另一个钥匙. 因为加密的钥匙是公开的,所又称公钥,解密的钥匙是不公开的,所以称为私钥. 密钥 关于RSA加密有很多 ...
- Shiro密码重试次数限制
如在 1 个小时内密码最多重试 5 次,如果尝试次数超过 5 次就锁定 1 小时,1 小时后可再次重试,如果还是重试失败,可以锁定如 1 天,以此类推,防止密码被暴力破解.我们通过继承 HashedC ...
- 微信小程序navigator带参数跳转及接收参数内容
// index.wxml <navigator class='looks-view' wx:for="{{imgUrlNew}}" wx:key="index&q ...