1. 程式人生 > >【dfs或者dp】等和的分隔子集

【dfs或者dp】等和的分隔子集


這道題我首先的思路是dfs,確實可以做,但是N上了30之後就執行超時了。說明dfs還是蠻容易超時的,適合小資料。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

long long allsum=0;
int cnt=0;
int N;
void dfs(int i,long long sum)
{
    if(sum==allsum/2)
    {
        cnt++;
        return;
    }
    if(sum>allsum/2)
    	return;
    for(int j=i-1;j>=1;j--)
    {
        sum+=j;
        dfs(j,sum);
        sum-=j;
    }
}



int main()
{
    cin>>N;
    for(int i=1;i<=N;i++)
        allsum+=i;
    if(allsum%2!=0)
    {
    	cout<<0;
    	return 0;
	}
    dfs(N,N);
    cout<<cnt;
    return 0;
}

後來去網上看了看其他人的思路,發現媽耶,可以用dp做。

因為這道題其實也可以類似成“裝東西”的題,所以很像01揹包的感覺。因此,重點是要找到dp陣列下標和數值含義以及狀態轉移方程。

神奇的思路在於,當dp[j]下標j表示當前子集的和為j,數值代表有多少個和為j的子集,那麼dp[j]=dp[j]+dp[j-i] ,這個i就是當前我應該放入的數。

分析:每個數字只用到一次,每種情況的存在數可以由之前的存在的數來遞推得到,01揹包的變形。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int allsum=0;
int cnt=0;
int N;
long long dp[10000];
int main()
{
    memset(dp,0,sizeof(dp));
    cin>>N;
    for(int i=1;i<=N;i++)
        allsum+=i;
    if(allsum%2!=0)
    {
    	cout<<0;
    	return 0;
	}
    dp[0]=1;  //!!!!空集也是集合!,重點是下面的迴圈裡當j=i時,dp[i]+=dp[0]的含義就是一個子集只要i一個就行,所以算一個方案數 加上。
    for(int i=1;i<=N;i++)  //向背包中(一定)放入i
    {
        for(int j=allsum/2; j>=i; j--)
        {
            dp[j]+=dp[j-i];
        }
    }
    cout<<dp[allsum/2]/2;
    return 0;
}