Description
子集和問題的一個實例為〈S,t〉。其中,S={ x1 , x2 ,…,xn }是一個正整數的集合,c是一個正整數。子集和問題判定是否存在S的一個子集S1,使得:
。
試設計一個解子集和問題的回溯法。
對於給定的正整數的集合S={ x1 , x2 ,…,xn }和正整數c,計算S 的一個子集S1,使得:
。
Input
輸入數據的第1 行有2 個正整數n 和c(n≤10000,c≤10000000),n 表示S 的大小,c是子集和的目標值。接下來的1 行中,有n個正整數,表示集合S中的元素。
Output
將子集和問題的解輸出。當問題無解時,輸出“No Solution!”。
Sample
Input
5 10
2 2 6 5 4
Output
2 2 6
題解:
遞歸回溯尋找子集,分為該數字在這個子集中和不在這個子集中兩種情況。遞歸也是通過這兩種情況進行的,但是注意“剪枝”,不然會超時。
而且這道題數據量十分大,就算剪枝也會超時。所以要多加一次判定。
另外這個題目可能存在多個解,然而數據偏弱,所以只要從第一個數據進行遞歸就能得到正解。其他解不用管。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define maxn 11234
using namespace std;
/**
*n集合大小
*k目標值
*s集合
*sum當前以累加值
*num當前子集的大小。
*flag標記是否已經找到子集。
*f記錄當前子集。
*/
int s[maxn], k, n, sum, num, flag, f[maxn];
void slove(int i){
//如果已經找到合適的子集,結束遞歸。
if(flag){
return;
}
int j;
//累加。
sum += s[i];
//當前數據進入子集。
f[num++] = s[i];
//大於目標值,,剪枝,結束遞歸。
if(sum > k)
return;
//已經找到合適的子集。
else if(sum == k){
flag = 1;
return;
}
//繼續還沒達到目標值,繼續累加。
for(j=i+1; j<n; j++){
slove(j);
if(!flag){
//說明當前數據不適合進入子集,回溯。
sum -= s[j];
num--;
}
else{
return;
}
}
}
int main(){
int i;
scanf("%d%d",&n,&k);
sum = 0;
//計算子集所有數據相加能否達到目標值,沒有這一步驟會超時。
for(i=0; i<n; i++){
scanf("%d",&s[i]);
sum += s[i];
}
if(sum < k){
printf("No Solution!\n");
return 0;
}
sum = num = flag = 0;
//開始進入遞歸。
for(i=0; i<n; i++){
slove(i);
if(!flag){
sum -= s[i];
num--;
}
else{
break;
}
}
if(flag){
for(i=0; i<num; i++){
printf("%d%c", f[i], i==num-1 ? '\n' : ' ');
}
}
else{
printf("No Solution!\n");
}
return 0;
}