Project Euler 103:Special subset sums: optimum 特殊的子集和:最优解

Special subset sums: optimum

Let S(A) represent the sum of elements in set A of size n. We shall call it a special sum set if for any two non-empty disjoint subsets, B and C, the following properties are true:

  1. S(B) ≠ S(C); that is, sums of subsets cannot be equal.
  2. If B contains more elements than C then S(B) > S(C).

If S(A) is minimised for a given n, we shall call it an optimum special sum set. The first five optimum special sum sets are given below.

n = 1: {1}
n = 2: {1, 2}
n = 3: {2, 3, 4}
n = 4: {3, 5, 6, 7}
n = 5: {6, 9, 11, 12, 13}

It seems that for a given optimum set, A = {a1, a2, … , an}, the next optimum set is of the form B = {b, a1+b, a2+b, … ,an+b}, where b is the “middle” element on the previous row.

By applying this “rule” we would expect the optimum set for n = 6 to be A = {11, 17, 20, 22, 23, 24}, with S(A) = 117. However, this is not the optimum set, as we have merely applied an algorithm to provide a near optimum set. The optimum set for n = 6 is A = {11, 18, 19, 20, 22, 25}, with S(A) = 115 and corresponding set string: 111819202225.

Given that A is an optimum special sum set for n = 7, find its set string.

NOTE: This problem is related to Problem 105 and Problem 106.


特殊的子集和:最优解

记S(A)是大小为n的集合A中所有元素的和。若任取A的任意两个非空且不相交的子集B和C都满足下列条件,我们称A是一个特殊的和集:

  1. S(B) ≠ S(C);也就是说,任意子集的和不相同。
  2. 如果B中的元素比C多,则S(B) > S(C)。

对于给定的n,我们称使得S(A)最小的集合A为最优特殊和集。前5个最优特殊和集如下所示。

n = 1: {1}
n = 2: {1, 2}
n = 3: {2, 3, 4}
n = 4: {3, 5, 6, 7}
n = 5: {6, 9, 11, 12, 13}

似乎对于一个给定的最优特殊和集A = {a1, a2, … , an},下一个最优特殊和集将是B = {b, a1+b, a2+b, … ,an+b}的形式,其中b是集合A“正中间”的元素。

应用这条“规则”,我们猜测对于n = 6的最优特殊和集将是A = {11, 17, 20, 22, 23, 24},相应的S(A) = 117。然而,事实并非如此,我们的方法仅仅只能找出近似最优特殊和集。对于n = 6,最优特殊和集是A = {11, 18, 19, 20, 22, 25},相应的S(A) = 115,对应的集合数字串是:111819202225。

若集合A是n = 7时的最优特殊和集,求其对应的集合数字串。

注意:此题和第105题第106题有关。

解题

题目坑了我好久

注意几点:

1.上面说的A的子集 B 和C ,B C 只是其中的任意两个子集,不是 B 和C的并等于A

2.A的任意子集都要满足上面两个条件,同时注意:B和C不能有交集

3.题目要求的是对于n的最优特殊和集,通过上面调整的是最近特殊和集,最优特殊和集在同样的n的情况下,其集合的和是最小的

4.7个数不相同,还是升序的

暴力解题:

1.在{19,30,37,38,39,41,44} 7个点附近寻找

2.求出所有的子集

3.对不相交的子集,利用题目给的两个条件进行求解

4.和最小的集合就是答案

下面程序参考了Mathblog,但是其是先求出每个子集的和,对于子集交集的没有看到,我改成求出所有的子集,再暴力找满足条件的值。

下面程序中在组合成初始集合A,最不好的方法也不是严格的在{19,30,37,38,39,41,44} 附近寻找的。同时,是用ArrayList当作集合用表示不是很好

JAVA

package Level4;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.TreeSet;


public class PE0103{
    
    // 对{20, 31, 38, 39, 40, 42, 45} //255  中的每个点+-3暴力求出所有的可能 
    // {19,30,37,38,39,41,44} //248
    public static void run(){
        int a[] = {19,30,37,38,39,41,44};
        int min = -3;
        int max = 3;
        int c1[] = {19 ,30, 37, 38, 39, 41,44};
        int c2[] = {19 ,31, 38, 39, 40, 42, 45};
        int c3[] = {20, 31, 38, 39, 40, 42, 45};
        int c4[] = {6, 9, 11, 12, 13};
        int c5[] = {11, 18, 19, 20, 22, 25};
        System.out.println(isOptimum(c1));
        System.out.println(isOptimum(c2));
        System.out.println(isOptimum(c3));
        System.out.println(isOptimum(c4));
        System.out.println(isOptimum(c5));
        for(int a0 = a[0] ;a0<=a[0]+ max ;a0++){
            for(int a1 = a0+1;a1<= a[1]+max ;a1++){
                for(int a2 = a1+1;a2<=a[2]+max ;a2++){
                    for(int a3 = a2+1;a3<=a[3]+max ;a3++){
                        for(int a4 = a3+1;a4<=a[4]+max ;a4++){
                            for(int a5 = a4+1;a5<=a[5]+max ;a5++){
                                for(int a6 = a5+1;a6<=a[6]+max ;a6++){
                                    int[] b= {a0,a1,a2,a3,a4,a5,a6};
                                    if(isOptimum(b)){
                                        String str = printArrStr(b);
                                        
                                        str = str + "	 SUM:"+sum(b);
                                        System.out.println(str);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

    }
    public static String printArrStr(int[] a){
        String str ="";
        for(int i=0;i<a.length;i++){
            str +=" "+a[i];
        }

        return str;
        
    }
    public static String printArrStr(ArrayList<Integer> list){
        String str ="";
        for(int i=0;i<list.size();i++){
            str += " "+list.get(i);
        }
        return str;
    }
    // 验证 是否是最优子集
    // 子集  一个集合可能有多个子集,而下面的程序只是看成两个自己的情况,两个子集的并集等于原来集合,照成程序有问题
    public static boolean isOptimum(int[] a){
        // 所有的子集
        ArrayList<ArrayList<Integer>> sets = MakeSubsets(a);
        int size = sets.size();
//        System.out.println(size);
        for(int i=0;i<size;i++){
            ArrayList<Integer> set1 = sets.get(i);
            for(int j=i+1;j<size;j++){
                ArrayList<Integer> set2 = sets.get(j);
                if(!isDisjoint(set1,set2)){
                    int sum1 = sum(set1);
                    int sum2 = sum(set2);
                    if(sum1 == sum2)
                        return false;
                    if(set1.size() > set2.size() && sum1 <=sum2)
                        return false;
                    if(set1.size() < set2.size() && sum1 >= sum2)
                        return false;
                }
            }
        }
        return true;
    }

    // 求集合内元素的和
    public static int sum(ArrayList<Integer> set){
        int sum = 0;
        for(int i=0;i< set.size();i++){
            sum += set.get(i);
        }
        return sum;
    }
    public static int sum(int [] a){
        int sum = 0;
        for(int i=0;i< a.length;i++){
            sum += a[i];
        }
        return sum;
    }
    // 两个子集元素是否相交 true 相交 false 不相交 
    public static boolean isDisjoint(ArrayList<Integer> set1,ArrayList<Integer> set2){
        int size1 = set1.size();
        int size2 = set2.size();
        ArrayList<Integer> set = new ArrayList<Integer>();
        for(int i=0;i<size1;i++){
            int element = set1.get(i);
            if(set.contains(element))
                return true;
            else
                set.add(element);
        }
        for(int i=0;i<size2;i++){
            int element = set2.get(i);
            if(set.contains(element))
                return true;
            else
                set.add(element);
        }
        set.clear();
        return false;
        
    }
    
    // 求出所有子集的和
    public static int[] MakeSubsetSums(int[] a){
        int b[] = new int[(int)Math.pow(2, a.length) ];
        for(int i=1;i<b.length;i++){
            b[i] = MakeSubsetSum(a,i);
        }
        return b;
    }
    // 求出所有的子集
    public static ArrayList<ArrayList<Integer>> MakeSubsets(int a[]){
        ArrayList<ArrayList<Integer>> sets = new ArrayList<ArrayList<Integer>>();
        for(int i=1;i<= (int) Math.pow(2,a.length);i++){
            ArrayList<Integer> set = MakeSubset(a,i);
            sets.add(set);
            String s = printArrStr(set);
//            System.out.println(s);
        }
        return sets;
            
    }
    // 求出子集
    public static ArrayList<Integer> MakeSubset(int[] a,int m){
        ArrayList<Integer> set = new ArrayList<Integer>();
        for(int i=0;i<a.length ;i++){
            if( m>0 &&(m&1)==1){
                set.add(a[i]);
            }
            m =m>>1;
        }
        return set;
    }
    // 求子集的和
    // 利用 和  1 进行与运算 并移位
    //  001001 相当于根据 1 所在的位置取 第 2 第 5的位置对应的数
    // &000001
    //----------
    //       1 取出该位置对应的数
    // 下面右移一位后
    //  000100
    // 下面同理了
    public static int MakeSubsetSum(int[] a,int m){
        int sum = 0;
        for(int i=0;i< a.length;i++){
            if( m>0 && (m&1) == 1)
                sum +=a[i];
            m >>=1;
        }
        return sum;
    }
    public static void main(String[] args){
        long t0 = System.currentTimeMillis();
        run();
        long t1 = System.currentTimeMillis();
        long t = t1 - t0;
        System.out.println("running time="+t/1000+"s"+t%1000+"ms");
    }
}

结果

 20 31 38 39 40 42 45     SUM:255
 20 32 39 40 41 43 46     SUM:261
 20 33 40 41 42 44 47     SUM:267
 21 32 39 40 41 43 46     SUM:262
 21 33 40 41 42 44 47     SUM:268
 22 33 40 41 42 44 47     SUM:269
running time=164s431ms

第一个就是答案,可以看出,直接根据第6个最优特征子集也可以推出下一个最优特殊子集

Python 质量也是好差

# coding=gbk

import time as time 
import re 
import math
import numpy as np 
def run():
    a= [19,30,37,38,39,41,44]
    f = lambda x:x+1
    a0 = map(f,range(16,22))
    a1 = map(f,range(27,34))
    a2 = map(f,range(34,41))
    a3 = map(f,range(35,42))
    a4 = map(f,range(36,43))
    a5 = map(f,range(37,44))
    a6 = map(f,range(41,48))
    for a in a0:
        for b in a1:
            for c in a2:
                for d in a3:
                    for e in a4:
                        for f in a5:
                            for g in a6:
                                if a<b and b<c and c<d and d<e and e<f and f<g:
                                    A = [a,b,c,d,e,f,g]
                                    if isOptimum(A):
                                        print A,'	',sum(A)
    
# [20, 31, 38, 39, 40, 42, 45]     255
# [20, 32, 39, 40, 41, 43, 46]     261
# [20, 33, 40, 41, 42, 44, 47]     267
# [20, 34, 37, 39, 40, 41, 48]     259
# [21, 32, 39, 40, 41, 43, 46]     262
# [21, 33, 40, 41, 42, 44, 47]     268
# [22, 33, 40, 41, 42, 44, 47]     269
# running time= 52.3980000019 s
def isOptimum(a):
    sets = subSets(a)
#     print len(sets)
    for i in range(len(sets)):
        for j in range(i+1,len(sets)):
            s1 = sets[i]
            s2 = sets[j]
            if isDisjoint(s1,s2) == False:
                sum1 = sum(s1)
                sum2 = sum(s2)
                len1 = len(s1)
                len2 = len(s2)
#                 print s1,'	',s2
                if sum1 == sum2:return False
                if len1>len2 and sum1<=sum2:return False
                if len1<len2 and sum1>=sum2:return False
    return True
def isDisjoint(b,c):
    for bi in b:
        if bi in c:
            return True
    return False

def subSets(a):
    l = 2**len(a)
    sets = []
    for i in range(1,l):
        s = subSet(a,i)
        sets.append(s)
    return sets
#   求一个子集
def subSet(a,m):
    s = []
    for i in range(len(a)):
        if (m&1)==1:
            s.append(a[i])
        m =m>>1 
    return s 
    
t0 = time.time()
run() 
t1 = time.time()
print "running time=",(t1-t0),"s"


            
原文地址:https://www.cnblogs.com/bbbblog/p/5141383.html