Как разбить множество на три подмножества с одинаковой суммой элементов?

Добрый день.

Встретилось задание из раздела о динамическом программировании, которое вызывает у меня трудности. Необходимо построить алгоритм, который по заданному множеству определяет, можно ли его разбить на три части, в которых суммы всех элементов будут равны. Например:
[1, 2, 3, 4, 4, 5, 8] -> {1,8}, {4,5}, {2,3,4}

Очевидно, что если сумма всех элементов не делится на 3, то множество не содержит такого разбиения (самая простая часть:) ). Но как действовать дальше? Вроде как мы должны проверять, к какому подмножеству у нас относится a_i элемент.
Но все это формализовать и прикрутить динамику не получается.
Буду признателен, если сможете направить на истинный путь.
  • Вопрос задан
  • 9139 просмотров
Решения вопроса 1
Mrrl
@Mrrl
Заводчик кардиганов
Задача о рюкзаке, даже о двух :)
Заводите массив M[0..S/3,0..S/3] (можно битовый, но лучше побольше - чтобы хранить обратные ссылки). В нём будут отмечены элементы M[a,b], такие, что из уже просмотренных элементов текущего набора можно выбрать непересекающиеся подмножества с суммами a и b.
Когда приходит очередной элемент x - выполняете M[a,b]=M[a,b] | M[a-x,b] | M[a,b-x] для всех a,b начиная с конца (с больших a,b). Если при этом 0 сменился на 1 - помечаете в массиве обратных ссылок, из какого элемента вы сюда попали.
Сложность - O(n*S^2). Можно ли сделать лучше, не знаю.

Вот вариант кода - но он печатает только два множества. Как напечатать третье, разберётесь.
spoiler
static void Main(string[] args) {
            int[] dat=Array.ConvertAll(Console.ReadLine().Split(' '),int.Parse);
            int S=0;
            foreach(int x in dat) S+=x;
            if(S%3!=0) {
                Console.WriteLine("-1"); return;
            }
            S/=3;
            int[,] M=new int[S+1,S+1];
            M[0,0]=-1;
            foreach(int x in dat) {
                for(int a=S;a>=0;a--) {
                    for(int b=S;b>=0;b--) {
                        if(M[a,b]==0) {
                            if(a>=x && M[a-x,b]!=0) M[a,b]=x;  // back by set1
                            else if(b>=x && M[a,b-x]!=0) M[a,b]=-x;  // back by set2
                        }
                    }
                }
            }
            if(M[S,S]==0) {
                Console.WriteLine("-1"); return;
            }
            int u=S,v=S;
            string res1="",res2="";
            while(u!=0 || v!=0) {
                if(M[u,v]>0) {
                    res1+=" "+M[u,v]; u-=M[u,v];
                } else {
                    res2+=" "+(-M[u,v]); v+=M[u,v];
                }
            }
            Console.WriteLine(res1);
            Console.WriteLine(res2);
        }

Ответ написан
Пригласить эксперта
Ответы на вопрос 3
AnnTHony
@AnnTHony
Интроверт
Внимательно читайте про разбиения. Зачем мудрить.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

#x = [1, 2, 3, 4, 4, 5, 8]
#x = [5, 7, 12, 3, 3, 1, 5]
x = [123, 1, 8, 4, 120, 59, 59, 10]

if (sum(x) % 3 == 0):
	tmp = sum(x) / 3
	x.sort()
	x.reverse()
	for i in range(3):
		y = []
		j = 0
		while (sum(y) != tmp):
			if ((sum(y) + x[j]) <= tmp):
				y.append(x[j])
				del(x[j])
			else:
				j += 1
		print(y)
else:
	print('Вознеможно разбить на 3 равные части!')


423e21783fc74175a7c2546ed9683cd5.jpg
Ответ написан
Olej
@Olej
инженер, программист, преподаватель
Но как действовать дальше?

Какой-то рекурсивный алгоритм, где:
1. набираем элементы до суммы S/3 в 1-е множество ...
2. для каждой найденной комбинации п.1 набираем элементы до суммы S/3 в 2-е множество ...
3. смотрим равна ли сумма того, что осталось S/3?
Дерево поиска.
Как вы не мудрите, итоговое решение будет вариантом такого.
Можно придумать что-то из жадных алгоритмов, чтобы сразу не углубляться в рекурсию на всю глубину.

Хорошая задача ...
Но громоздкая, поэтому сюда не помещаю.
Вот вариант решения: примеры задач при изучении C++
$ ./3set
Вводите числа построчно (пустая строка - конец ввода):
1 2 3 4
4 5 8

{ 1, 2, 3, 4, 4, 5, 8 } ->
{ 1, 8 }
{ 4, 5 }
{ 2, 3, 4 }
Ответ написан
Комментировать
streetflush
@streetflush
Решил как смог )
function summ(a){
 if(a.length==0){return 0}
 else{res = 0; for(var i=0;i<a.length;i++){res=res+a[i]} return res;}
}
 var ar = [5, 7, 12, 3, 3, 1, 5]; 
ar=ar.sort(function(a, b){return a-b}); 
var b=[];
var c =[];
 var d=[]; 
for(var i=ar.length-1;i>-1;i--){
if(summ(b)+ar[i]<=summ(ar)/3) {b.push(ar[i])}
else if(summ(c)+ar[i]<=summ(ar)/3) {c.push(ar[i])}
else if(summ(d)+ar[i]<=summ(ar)/3) {d.push(ar[i])}}; 
console.log(b);console.log(c);console.log(d)


Чет мне кажется что не всегда будет работать)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы