간단한 프로그램인데요.... 더 최적화 하고 싶습니다.;;

zilitwo의 이미지

문제를 하나 구했습니다.
문제는

예를들어 입력이 3 이면

1, 2, 3 으로 두개의 집합을 만들어서 두 집합의 수가 같은 집합의 갯수를 구하는건데요.

3 이 입력이 되면
1, 2, 3 이 있으니까 정답은

{1, 2} 와 {3} 한개가 답이구요.

7 이 입력이 되면
1, 2, 3, 4, 5, 6, 7 을 이용해서 두개의 집합으로 나눠서 두 집합의 숫자의 합이 같도록 되어야 합니다.

두개 집합으로 나눴을때, 각 집합의 합이 14가 되는 경우의 수를 구하면 되는 것이지요..

좀더 간단하게 하면

1, 2, 3, 4, 5, 6, 7 을 이용해서 합이 14가 되도록 숫자들을 뽑을수 있는 경우의수/2 가 정답이 되는것이지요..

이때 입력된 값이 1 부터 39 까지 가능하다고 합니다.

일단 아래는 제가 푼 소스구요.
23까지는 답이 금방 나오는데 39는 답이 한참 걸릴것 같네요..

39라면 실제 비교횟수가 2^39 가 되는데
이정도 숫자면 아무일도 안하고 for 문만 돌려도 수행시간이 상당히 오래 걸립니다.

그렇다면 이 문제는 프로그램으로가 아닌 수학적으로 최적화가 되어야 할것 같은데..
좋은방법이 없을까요?

// subset.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
 
// int 형 배열, 숫자는 1부터 39 사이에서 입력이 되기때문에 배열은 40개만 잡으면 된다.
// 예를 들어 set_side[10] 의 값이 0 이면 숫자 10은 왼쪽 집합에 포함된 상태이고
// set_side[10] 의 값이 1 이면 숫자 10은 오른쪽 집합에 포함된 상태라고 정의한다.
int set_side[40];
int result_count = 0;
int lSum = 0;
int rSum = 0;
 
void preCalculateSum( int input )
{
	for( int i = 1; i <= input;i++ ) {
		if( set_side[i] == 0 )
			lSum += i;
		else
			rSum += i;
	}
}
 
void AddCount( int input )
{
	if( set_side[input] == 0 ) {
		set_side[input] = 1;
		// 카운트를 올리면서 각 집합의 합을 실시간으로 구한다.
		lSum -= input;
		rSum += input;
	}
	else {
		// 카운트를 올리면서 각 집합의 합을 실시간으로 구한다.
		rSum -= input;
		lSum += input;
		set_side[input] = 0;
		AddCount( input-1 );
	}
}
 
void GetInput( int* input )
{
	scanf( "%d", input );
}
 
void Output( int out )
{
	printf( "result : %d\n", out );
}
 
int _tmain(int argc, _TCHAR* argv[])
{
	int input;
	GetInput( &input );
 
	if( input < 1 || input > 39 ) {
		printf("input value : %d, Out of range\n", input );
		Output( 0 );
		return 0;
	}
 
	// 1부터 입력된 숫자까지 더한 값이 2로 나누어 떨어지지 않으면 해볼것도 없이 결과는 안나온다.
	if( (((input+1)*input)/2) % 2 != 0 ) {
		Output( 0 );
		return 0;
	}
 
 
 
	// 모든 경우의 수는 예를 들어 input 이 3 이라면
	// 0 0 0
	// 0 0 1
	// 0 1 0
	// 0 1 1
	// 1 0 0
	// 1 0 1
	// 1 1 0
	// 1 1 1
	// 순서대로 비트배열에서 숫자를 1씩 더했을때 이진수를 카운트 하듯이 올라가서
	// 0 0 0 부터 1 1 1 까지 가면 모든 숫자가 왼쪽, 오른쪽 각 집합에 대입이 될 수 있다.
 
 
	// 40개 배열중에서 실제로 사용할 부분만 0으로 설정
	for( int i = 1; i <= input; i++ ) {
		set_side[i] = 0;
	}
 
	// 몇번 비교를 해야 하는지 미리 값을 구한다.
	double count = 1;
	for( int i = 0;i < input-1;i++ )
		count *= 2;
 
	preCalculateSum( input );
 
	for( int i = 0;i < count;i++ ) {
		AddCount( input );
		if( lSum == rSum )
			result_count++;
	}
 
 
	// 결과는 예를 들어 7 이 입력이 되었을때
	// {1, 6, 7} 과 {2, 3, 4, 5} 그리고
	// {2, 3, 4, 5} 와 {1, 6, 7} 인경우가 모두 계산이 되기때문에 결과를 2로 나누어야 정확한 결과가 나온다.
	// 이를 효율적으로 중복 계산이 되지 않도록 하기위해서는
	// 숫자 1은 무조건 왼쪽 집합에 포함이 되도록 고정을 하고
	// CheckSumOfSet 함수에서도 숫자 1 만 0 인상태를 모든 상태가 다 검사된 상태로 되도록 변경 한다면
	// 계산시간을 절반으로 줄이고 결과는 2로 나눌 필요가 없게 된다.
	Output( result_count );
 
	return 0;
}

auditory의 이미지

..

zilitwo의 이미지

2^39 는
549755813888 인데요;;

오천사백억이 넘습니다;;;

-----------------------------------
속좀 썩이지 마라~~ 잉???

auditory의 이미지

잠깐 제가 착각해서 엉뚱한 이야기를 했습니다.. ^^
바로 지웠는데 그 사이에 글을 보신 모양이네요..

아래 redneval 님의 방법이 멋지네요..

f_{N,S} = {1,2,...,N}의 부분집합 중 원소의 합이 S가 되는 집합의 수라고 정의하면

f_{N+1,S} = f_{N,S} + f_{N, S-(N+1)} 을 이용하는군요..

재밌네요..

redneval의 이미지

0.1초에서 0.2초쯤 걸립니다.

실행결과 :

1 ~ 3 (sum=3)   1
1 ~ 4 (sum=5)   1
1 ~ 7 (sum=14)  4
1 ~ 8 (sum=18)  7
1 ~ 11 (sum=33) 35
1 ~ 12 (sum=39) 62
1 ~ 15 (sum=60) 361
1 ~ 16 (sum=68) 657
1 ~ 19 (sum=95) 4110
1 ~ 20 (sum=105)        7636
1 ~ 23 (sum=138)        49910
1 ~ 24 (sum=150)        93846
1 ~ 27 (sum=189)        632602
1 ~ 28 (sum=203)        1199892
1 ~ 31 (sum=248)        8273610
1 ~ 32 (sum=264)        15796439
1 ~ 35 (sum=315)        110826888
1 ~ 36 (sum=333)        212681976
1 ~ 39 (sum=390)        1512776590

코드 :

#!/usr/bin/perl
use strict;

my $Solution_ref;
$Solution_ref->[0][0] = 1;

my $ITER = 39;
foreach my $m (1..$ITER) {
	foreach my $n (0..($m+1)*$m/2) {
		if($n == 0 || $n == ($m+1)*$m/2) {
			$Solution_ref->[$m][$n] = 1;
		}
		else {
			$Solution_ref->[$m][$n]	= f($m-1,$n) + f($m-1,$n-$m);
		}
	}
}
foreach my $m (1..$ITER) {
	if($m %4 == 3 || $m %4 == 0) {
		print "1 ~ $m (sum=", ($m+1)*$m/2/2, ")\t", $Solution_ref->[$m][($m+1)*$m/2/2]/2, "\n";
	}
}

sub f {
	my ($m, $n) = @_;
	if($n == 0 || $n == ($m+1)*$m/2) {
		return 1;
	}
	elsif($n < 0 || $n > ($m+1)*$m/2) {
		return 0;
	}
	return $Solution_ref->[$m][$n]
}

--------------------Signature--------------------
Light a candle before cursing the darkness.

blkstorm의 이미지

It looks like subset-sum problem, one of the NP Complete problem.

Try to find articles on subset-sum problem or exact-cover problem.

redneval의 이미지

이 문제는 `subset-sum problem' 과는 다릅니다.

그리고 제가 푼 방식을 보면 아시겠지만

푸는데 Polynomial time 이 소요되므로

NP (Non-deterministic Polynomial time) 가 아니고 P 입니다.

--------------------Signature--------------------
Light a candle before cursing the darkness.

bookgekgom의 이미지

------------------------------------------------------
허접한 페도라 가이드 http://oniichan.shii.org

---------------------------------------------------------------------------------------------------------------
루비 온 레일즈로 만들고 있는 홈페이지 입니다.

http://jihwankim.co.nr

여러 프로그램 소스들이 있습니다.

필요하신분은 받아가세요.

leonid의 이미지

def n(a,s)a<2?16[s]:n(a-1,s)+n(a-1,s-a*4)end;p n i=gets.to_i,i*i+i

66바이트 루비 코드입니다. -_-;;;;

코드만 줄이다보니 속도는 엄청 느려요 ㅋㅋ

redneval의 이미지

여기서 푸는 것보다는 www.codegolf.com 에 이 문제를 제보(?)해서 전세계인과 함께 즐기는 건 어떨까요?

--------------------Signature--------------------
Light a candle before cursing the darkness.

leonid의 이미지


근데 어떤식으로 문제를 만들어야 할지 감이 안잡히네요

본문에 있는것과 똑같은 문제를 만들면 문제의 의도를 벗어난 편법이 많이 쓰일 겁니다.

그리고 무엇보다 막상 등록이 되고나면 기록 보존을 위해서

자신의 코드를 공개하기를 꺼리는 경향이 생기는것 같습니다.. 그건 쫌 그렇네요 ㅎㅎ

redneval의 이미지

다음과 같은 출력을 요구하면 되지 않을까요.

3:1
4:1
7:4
8:7
11:35
12:62
15:361
16:657
19:4110
20:7636
23:49910
24:93846
27:632602
28:1199892
31:8273610
32:15796439
35:110826888
36:212681976
39:1512776590

Quote:

자신의 코드를 공개하기를 꺼리는 경향이 생기는것 같습니다.. 그건 쫌 그렇네요 ㅎㅎ

코드가 공개되면 더 재미없는거 아니였나요. :)

--------------------Signature--------------------
Light a candle before cursing the darkness.

leonid의 이미지

한번 풀어봤는데 제 예상과는 달리 다행히 unpack('w*')을 이용한 편법이 통하지 않네요 -_-ㅋ

출력본 중간중간에 빠진 1:0, 2:0, 5:0, 6:0 도 넣으면 코딩하기 편하고 좋을것 같습니다 ㅎㅎ

Quote:

코드가 공개되면 더 재미없는거 아니였나요. :)

아 그리고 코드를 공개하면 서로의 노하우를 공유하게 되어서 실력이 증가하죠.. ㅎㅎ

그리고 전 같은 문제가 얼마나 다른 방식으로 풀리는지를 볼 수 있어서 재밌던데말입니다 -_-ㅋ

doodoo의 이미지

#!/bin/sh
echo "
3:1
4:1
7:4
8:7
11:35
12:62
15:361
16:657
19:4110
20:7636
23:49910
24:93846
27:632602
28:1199892
31:8273610
32:15796439
35:110826888
36:212681976
39:1512776590
"

죄송....ㅜㅜ 후다닥 3=3=33=