project euler 103

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题有关。

package projecteuler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import junit.framework.TestCase;

public class Prj103 extends TestCase {

	List<int[]> resultList = new ArrayList<int[]>();

	/**
	 * simple dfs;
	 * search start from n-1;
	 * k + (k + 1) > k + n -1
	 */
	public void testSpecialSubsetSumsOptimum() {
		int[] arr = new int[7];
		dfs(arr, 0, arr.length - 1, 50 );
		System.out.println("size=" + resultList.size());
		
		int sum = Integer.MAX_VALUE;
		int minId = 0;

		for( int i = 0 ; i < resultList.size() ; i ++){
			int[] _val = resultList.get(i);
			int val = sumArr(_val);
			if( val < sum){
				sum = val;
				minId = i;
			}
		}
		
		printArr(resultList.get(minId));
		System.out.println("sum=" + sumArr(resultList.get(minId)) );

	}

	public void dfs(int[] arr, int id, int start, int end) {

		if (id >= arr.length) {
			if (checkOk(arr)) {
				resultList.add(Arrays.copyOf(arr, arr.length));
				printArr(arr);
			}
			return;
		}

		for (int i = start; i <= end; i++) {
			if (id == 0) {

				arr[id] = i;
				dfs(arr, id + 1, start, end);

			} else {

				if (i <= arr[id - 1]) {
					continue;
				}

				arr[id] = i;
				dfs(arr, id + 1, start, end);
			}

		}

	}

	int sumArr(int arr[]) {
		int sum = 0;
		for (int val : arr) {
			sum += val;
		}
		return sum;
	}

	private boolean checkOk(int[] arr) {

		if (Arrays.equals(arr, new int[] { 11, 18, 19, 20, 22, 25 })) {
			System.out.println();
		}
		assert (arr.length >= 5);
		int n = arr.length;

		int sumAsc = arr[0] + arr[1];
		int sumDesc = arr[n - 1];
		int count = 3;
		int c = 2;
		while (true) {
			if (count > n) {
				break;
			}
			if (sumAsc <= sumDesc) {
				return false;
			}
			sumAsc += arr[c];
			sumDesc += arr[n - c];
			c++;
			count += 2;

		}

		Set<Integer> set = new HashSet<Integer>();
		int sum = 0;
		for (int val : arr) {
			set.add(val);
			sum += val;
		}
		set.add(sum);

		for (int p = 2; p <= n / 2; p++) {
			Comb cb = new Comb(n, p);
			List<int[]> cbs = cb.calculateComb();

			for (int i = 0; i < cbs.size() - 1; i++) {
				int[] _cb = cbs.get(i);

				int sum1 = 0;
				int sum2 = 0;
				for (int j = 0; j < _cb.length; j++) {
					if (_cb[j] == 1) {
						sum1 += arr[j];
					} else {
						sum2 += arr[j];
					}
				}

				if (set.contains(sum1)) {
					return false;
				}
				set.add(sum1);

				if (p == n / 2 && n % 2 == 0) {
					continue;
				}

				if (set.contains(sum2)) {
					return false;
				}
				set.add(sum2);
			}

		}

		return true;
	}

	void printArr(int[] diff) {
		for (int i = 0; i < diff.length; i++) {
			System.out.print(diff[i] + "\t," );
		}
		System.out.println();
	}

	/**
	 * 组合
	 * 
	 * @author suc
	 *
	 */
	public static class Comb {

		private int n;
		private int p;

		public Comb(int n, int p) {
			this.n = n;
			this.p = p;
		}

		public List<int[]> calculateComb() {

			assert (p < n && p > 0);

			List<int[]> ret = new ArrayList<int[]>();

			int[] arrs = new int[n];
			for (int i = 0; i < p; i++) {
				arrs[i] = 1;
			}

			ret.add(Arrays.copyOf(arrs, arrs.length));

			for (;;) {

				boolean find = findNext(arrs);
				ret.add(Arrays.copyOf(arrs, arrs.length));
				if (!find) {
					break;
				}

			}

			return ret;

		}

		private boolean findNext(int[] arrs) {

			int count = 0;
			int find = -1;

			for (int i = 0; i < arrs.length - 1; i++) {
				if (arrs[i] == 1) {
					count++;
				}

				if (arrs[i] == 1 && arrs[i + 1] == 0) {

					int tmp = arrs[i + 1];
					arrs[i + 1] = arrs[i];
					arrs[i] = tmp;
					find = i;
					break;
				}

			}

			count--;

			for (int i = 0; i < find; i++) {
				if (i < count) {
					arrs[i] = 1;
				} else {
					arrs[i] = 0;
				}
			}

			return !(find == -1);
		}

	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值