算法設計與分析——划分問題(動態規划)


Description


給定一個正整數的集合A={a1,a2,….,an},是否可以將其分割成兩個子集合,使兩個子集合的數加起來的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10},

Input

第一行集合元素個數n  n <=300 第二行n個整數
 
Output

如果能划分成兩個集合,輸出任意一個子集,否則輸出“no”

Sample Input

5
1 3 8 4 10

Sample Output

3 10

 

這道題是本校OJ上的一道題,作為算法設計與分析課程的作業,要求使用動態規划,我最開始的想法是將這個問題往背包問題上轉換,思路應該是正確的,不過一開始沒能實現,最終還是寫出來了,但這里要介紹一種老師講的方法。

用數組a[i]來存儲正整數的集合。

構造一個二維數組表t[i][j],表示{a1,a2......ai}存在子集和為j,並將其值賦為真。

這句話該怎么理解呢?

比如此時ai={3,4,8,1,10}

a1=3,a2=4,a3=8,a4=1,a5=10

那么{a1,a2,a3}構成的子集有{∅}、{a1}、{a2}、{a3}、{a1,a2}、{a2,a3}、{a1,a3}、{a1,a2,a3},對應的子集和為0,3,4,8,7,12,11,15。

這樣就可以得到t[3][0]=1 t[3][3]=1 t[3][4]=1 t[3][8]=1 t[3][7]=1 t[3][12]=1 t[3][11]=1 t[3][15]=1

看到這里是不是就應該明白這個二維數組的含義了吧,我們只要求得t[n][sum/2]=1,說明{a1,a2......an}存在一組子集正數其和為sum/2,(這里的sum表示整個整數集合之和),也就是說存在兩個子集合加起來的和相等。

 

既然是動態規划我們需要探究一下其遞歸方程應該如何表示

如果t[i-1][j]=1       那么一定有t[i][j]=1  因為前i-1個正整數的子集能夠組成的和為j,那么再加上第i個正整數,還是用之前的子集,能夠組成和還是j。

如果t[i-1][j-a[i]]=1  那么也會有t[i][j]=1  因為前i-1個正整數的子集能夠組成的和為j-a[i],那么再加上第i個正整數,還是用之前的子集再加上a[i],能夠組成和j。

由此我們得到了遞歸迭代方程:

t[1][0] =1

t[1][a[1]] =1

t[i][j] =1 《==( t[i-1][j]=1 || t[i-1][j-a[i]]=1 )

 

還是以上面的ai={3,4,8,1,10}為例子,寫一下t[i][j]的二維數組表

我們根據t[n][sum/2]的值就能判斷是否存在兩個相等的子集和,但怎么樣確定子集呢?

確定子集的方法有很多,OJ后台只需要一種答案即可,這里講一下我使用的方法。

我的想法就是觀察上面的表看看sum/2是有那些元素貢獻的,其實上面的表是從左上角到右下角打印的,我們從后向前逆推組成元素時就需要從右下角向左上角推理,發現t[4][13]是13列第一個帶有真值的,最終結果t[5][13]也為真,說明a[4]肯定是納入子集中,sum/2-a[4]后,也就是13-1=12,我們再來看t[3][12]是12列第一個帶有真值的,說明a[3]肯定被納入子集中,12-a[3],也就是12-8=4;我們再來看t[2][4]是第4列第一個帶有真值的,說明a[2]肯定被納入子集中,4-a[2],也就是4-4=0,至此子集中所有的元素都找到了。為1、8 、4

 

 

之前有同學不明白第一張表中的數據是怎么來的,這里我再畫一一張表,追蹤一下動態規划的過程。

 

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
int main()
{
    int n;
    int sum=0;
    int a[310];
    int ans[155];
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    if(sum%2!=0)
    {
        printf("no\n");
        return 0;
    }
    int m=sum/2;
    int dp[n+1][m+1];
    memset(dp,0,sizeof(dp));
    dp[1][0]=1;
    dp[1][a[1]]=1;
    for(int i=2; i<=n; i++) //前i個元素
    {
        for(int j=0; j<=m; j++) //可以構成j
        {
            if(dp[i-1][j]||dp[i-1][j-a[i]])
            {
                dp[i][j]=1;
            }
        }
    }
    if(!dp[n][m])
    {

        printf("no\n");
        return 0;
    }
    int cnt=0;
    for(int j=m; j>=0; j--)
    {
        for(int i=n; i>=1; i--)
        {
            if(dp[i][j]&&!dp[i-1][j])//二維數組中每一列最頂部的那個T
            {
                ans[cnt]=a[i];
                cnt++;
                j=j-a[i];
                if(j==0)//找完結束
                {
                    break;
                }
            }
        }
    }
    for(int i=0; i<cnt-1; i++)
    {
        printf("%d ",ans[i]);
    }
    printf("%d\n",ans[cnt-1]);
    return 0;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM