| ||||||||||
| Online Judge | Problem Set | Authors | Online Contests | User | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Web Board Home Page F.A.Qs Statistical Charts | Current Contest Past Contests Scheduled Contests Award Contest | |||||||||
贴Java代码。 半枚举+二分,带注释// 如果是n个值进行枚举的话计算量是O(2^n),绝壁hold不住
// 所以把这些值分成两半,分别枚举,然后在组合这两个数组的时候用二分法避免了全列举。
// 当时做这道题目的时候写到一半发现不太对头,所以大改过(只比重写稍微轻松了一点点)所以代码看起来比较蛋疼且冗余。。。
//A了就懒得改了。嫌麻烦的只看注释吧2333
import java.io.*;
import java.util.*;
public class Main{
static int N;
// 记录输入的数字
static long nums[];
// 这两个动态数组分别记录分成两半后各自枚举的所有情况
static ArrayList<subset> ssL = new ArrayList<subset>();
static ArrayList<subset> ssR = new ArrayList<subset>();
// 这两个数组用于枚举(取不取当前数字?取的话设为1,不取设为0)
static int[] iruL;
static int[] iruR;
// 最小的绝对值
static long ans;
// 最小的元素个数
static int ansc;
// 下面2个函数把N个值分成两半然后分别枚举,各自最多为2^17,2^18种情况
static void generate_subsetL(int index){
if(index == N/2){
long sum = 0;
int count = 0;
for(int i = 0; i < iruL.length; i++){
if(iruL[i] == 1){
count++;
sum += nums[i];
}
}
ssL.add(new subset(sum,count));
return;
}
// 用递归进行枚举
for(int i = 0; i < 2; i++){
iruL[index] = i;
generate_subsetL(index+1);
}
}
static void generate_subsetR(int index){
if(index == N-N/2){
long sum = 0;
int count = 0;
for(int i = 0; i < iruR.length; i++){
if(iruR[i] == 1){
count++;
sum += nums[i+N/2];
}
}
ssR.add(new subset(sum,count));
return;
}
for(int i = 0; i < 2; i++){
iruR[index] = i;
generate_subsetR(index+1);
}
}
// 组合两个数组。因为ssL数组规模小于等于ssR,所以对ssL进行了排序
// 然后循环ssR中每个元素,二分ssL
private static void solve() {
// 循环ssR的所有元素
for(int q = 0; q < ssR.size(); q++){
long sum = ssR.get(q).sum;
int count = ssR.get(q).count;
// 用二分法逼近答案,因为可能会有多解的情况
// 所以需要进行两次,第一次寻找最接近0的负值,第二次寻找最接近0的正值。
// 然后扫描这两者之间的所有元素,找出sum最小,且count最小的组合
// 先找最接近0的负值,二分后取左边界
int l = 0;
int r = ssL.size();
while(r-l>1){
int m = (l+r)/2;
if(ssL.get(m).sum + sum >=0){
r = m;
}else{
l = m;
}
}
int L = l;
// 然后找最接近0的正值,二分后取右边界
l = -1;
r = ssL.size()-1;
while(r-l>1){
int m = (l+r)/2;
if(ssL.get(m).sum + sum >0){
r = m;
}else{
l = m;
}
}
int R = r;
// 扫描L到R中所有情况,与ans对比
// L到R最少1种情况,最多13w种,不过只要出现ans=0,ansc=1就可以直接输出了。不可能出现要扫那么多情况的情况。
for(int i = L; i <= R; i++){
long tempsum = Math.abs(ssL.get(i).sum + sum);
int tempcount = ssL.get(i).count + count;
// 排除掉空集的情况
if (tempcount == 0) continue;
if(tempsum < ans){
ans = tempsum;
ansc = tempcount;
}
else if(tempsum == ans & tempcount < ansc){
ansc = tempcount;
}
if(ans == 0 & ansc == 1)
return;
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(true){
N = new Integer(br.readLine());
if(N==0) break;
nums = new long[N];
iruL = new int[N/2];
iruR = new int[N-N/2];
ans = 1000000000000001l;
ansc = 36;
String input[] = br.readLine().split("\\s");
for(int i = 0; i < N; i++){
nums[i] = new Long(input[i]);
}
// 分成两半后分别枚举
ssL.clear();
ssR.clear();
generate_subsetL(0);
generate_subsetR(0);
// 因为ssL数组规模小于等于ssR,所以对ssL进行了排序
Collections.sort(ssL);
solve();
System.out.println(ans+" "+ansc);
}
}
}
//子集。变量是sum:该子集的元素和,count:该子集的元素个数
class subset implements Comparable<subset>{
long sum;
int count;
subset(long sum, int count){
this.sum = sum;
this.count = count;
}
public int compareTo(subset o) {
if(this.sum - o.sum < 0)
return -1;
else
return 1;
}
}
Followed by: Post your reply here: |
All Rights Reserved 2003-2013 Ying Fuchen,Xu Pengcheng,Xie Di
Any problem, Please Contact Administrator