P1064-子集和問題

子集和問題
描述 Description
【問題描述】
  子集和問題的一個實例爲〈S,t〉。其中,S={ x1, x2,…, xn}是一個正整數的集合,c是一個正整數。子集和問題判定是否存在S的一個子集S1,使得子集S1和等於c。
【編程任務】
  對於給定的正整數的集合S={ x1, x2,…, xn}和正整數c,編程計算S 的一個子集S1,使得子集S1和等於c。
【輸入格式】
  由文件subsum.in提供輸入數據。文件第1行有2個正整數n和c,n表示S的個數,c是子集和的目標值。接下來的1 行中,有n個正整數,表示集合S中的元素。
【輸出格式】
程序運行結束時,將子集和問題的解輸出到文件subsum.out中。當問題無解時,輸出“No Solution!”。
【輸入樣例】
5 10
2 2 6 5 4
【輸出樣例】
2 2 6

時間限制 Time Limitation
各個測試點1s

這是一個子集樹問題。如果用常規深搜遞歸解法的話,很顯然時間複雜度太高

#include<iostream>
#include<string.h>
#include<stdlib.h>

using namespace std;

int total=0;
int n,num[10001],k=0;
bool map[10001];
int arr[10001]={0};
void print()
{
    for (int i=1;i<=k;i++)
    {
        cout<<arr[i]<<" ";
    }
}

void search(int m,int h)
{
     if(m==0) {
        print();total++;exit(0);
     }
     for(int i=1;i<=n;i++)
     {
        if(m-num[i]>=0&&map[i]==true&&i>h)
        {
            map[i]=false;
             k++;
            arr[k]=num[i];
            search(m-num[i],i);
            arr[k]=0;
            k--;
            map[i]=true;
        }
     }
}

int main()
{
//時間複雜度爲O(2^n)
    int m;
    cin>>n>>m;
    for (int i=1;i<=n;i++)cin>>num[i];
    memset(map,true,sizeof(map));
    search(m,0);
    if(total==0)cout<<"No Solution!";
}

看到http://blog.sina.com.cn/s/blog_7865b083010100dd.html有一個非遞歸回溯算法:

但是很顯然窮舉所有子集的複雜度是2^n對於這個複雜度,計算機是很難承受的。到規模超過30的時候 ,已經幾乎出不了結果了。所以採用一種更加快速的非遞歸回溯算法。它的思想是,從第一個元素開始,如果此時當前的元素不在集合內的話,將這個元素加到子集當中來(用visited數組標記) ,將sum加上這個元素的值。然後判斷如果sum恰好爲目標值c的話,就返回正值並且打印結果。如果sum > c 的話則捨棄當前這個元素,修改標記數組,並且將sum減去這個元素的值。只要還有元素沒有判斷就繼續選擇。直到第n個元素,如果第n個元素判斷完還沒有找到解的話,就回溯到上一次選擇的那個點,將其從集合裏面刪除並從它後一個點繼續重複前面的操作。如果回溯的時候回溯到了第一個元素之前的話呢,表示這個時候要麼所有元素都加入到集合都不夠,或者是所有的情況都找過了還是沒有解決方案,這個時候返回無解。代碼如下:


#include<stdio.h>


#define MAX 10000

int data[MAX] ;
bool v[MAX] ;
int n , c ;
bool traceback(int n){
int p = 0 ,sum = 0 ;
while(p>=0)
{
if(!v[p])
{
v[p] = true ;
sum += data[p] ;
if(c == sum)
return true ;
else if( c < sum)
{
v[p] = false ;
sum -=data[p] ;
}
p++ ;
}
if(p>=n)
{
while( v[p-1] )
{
p-- ;
v[p] = false ;
if(p<1) return false ;
}
while( !v[p-1])
{
p-- ;
if(p<1) return false ;
}
sum -= data[p-1] ;
v[p-1] = false ;
}
}
return false ;
}
int main(){
scanf("%d %d" , &n , &c) ;
for(int i = 0 ; i < n ; i++)
scanf("%d" , &data[i]) ;
if(traceback(n))
{
int first = 1 ;
for(int i = 0 ; i < n; i++)
if(v[i])
{
if(first)
first = 0 ;
else
printf(" ") ;
printf("%d" , data[i]) ;
}
printf("\n") ;
}
else
printf("No Solution!\n") ;
return 0 ;
}

對於這樣的子集和問題,應該用dfs+子集樹來完成。比如:
http://blog.csdn.net/qq_27601815/article/details/53454017

然而似乎是數據太水。我改了一下最上面的程序:

#include<iostream>
#include<string.h>
#include<stdlib.h>

using namespace std;

int total=0;
int n,num[10001],k=0;
bool map[10001];
int arr[10001]={0};
void print()
{
    for (int i=1;i<=k;i++)
    {
        cout<<arr[i]<<" ";
    }
}

void search(int m,int h)
{
     if(m==0) {
        print();total++;exit(0);
     }
     for(int i=1;i<=n;i++)
     {
        if(m-num[i]>=0&&map[i]==true&&i>h)
        {
            map[i]=false;
             k++;
            arr[k]=num[i];
            search(m-num[i],i);
            arr[k]=0;
            k--;
            map[i]=true;
        }
     }
}

int main()
{
    int m;
    cin>>n>>m;
    int total=0;
    for (int i=1;i<=n;i++)
    {
        cin>>num[i];
        total+=num[i];
    }
    if (total<m)
    {
        cout<<"No Solution!";
        return 0;
    }//剪枝1
    if (total==m)
    {
        for (int i=1;i<=n;i++)
        cout<<num[i]<<" ";
        return 0;
    }//剪枝2
    memset(map,true,sizeof(map));

    search(m,0);
    if(total==0)cout<<"No Solution!";
}

其實也就加了一兩個判斷,然而在平臺上測試的結果差距很大
這裏寫圖片描述

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章