等和的分隔子集(01揹包)
阿新 • • 發佈:2019-01-03
曉萌希望將1到N的連續整陣列成的集合劃分成兩個子集合,且保證每個集合的數字和是相等。例如,對於N=3,對應的集合{1,2,3}能被劃分成{3} 和 {1,2}兩個子集合.
這兩個子集合中元素分別的和是相等的。
對於N=3,我們只有一種劃分方法,而對於N=7時,我們將有4種劃分的方案。
輸入包括一行,僅一個整數,表示N的值(1≤N≤39)。
輸出包括一行,僅一個整數,曉萌可以劃分對應N的集合的方案的個數。當沒發劃分時,輸出0。
樣例輸入
7
樣例輸出
4
計蒜客上面的一道題。
主要就是兩個for迴圈,第一個for迴圈,枚舉出1-n,第二個for迴圈,列舉他們的累加和,然後更新當前1-i個數累加的方案數
dp[i][j]的意思是,用前i個數累積到j,有多少種方案
dp[i][j] = dp[i-1][j]+dp[i-1][j-i];
這個轉移方程的意思是,當前的方案數等於 上一個累積到j的方案數 + 上一個累積到j-i的方案數
例如 i = 3時,首先dp[3][0] = 1,dp[3][1] = 1, dp[3][2] = 1(這幾個都是直接複製前面的), 然後dp[3][3] = 1+1 = 2
就是本來已經湊成3的方案數 + 加入3後湊成3的方案數 = 2;
還有個問題就是 基層為1,即 dp[1][0] = 1,dp[2][1] = 1,dp[3][i] = 1,................
至於為什麼,是為了統一轉移方程,然後就需要自己去仔細想想了
以後一定回來看一遍,一定!!!
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #include<cmath> #include<algorithm> #include<list> #include<map> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; ll dp[42][4500]; int main() { int n; while( scanf("%d",&n) == 1 ) { ll goal = (ll)( n*(n+1)/2); if( goal & 1 ) //如果和是一個奇數,則不能分成兩份 { puts("0"); continue; } goal = goal>>1; memset(dp,0,sizeof dp); dp[0][0] = 1; for( int i = 1 ; i <= n ; i++ ) { for( int j = 0 ; j <= goal ; j++ ) { if( j >= i ) dp[i][j] = dp[i-1][j]+dp[i-1][j-i]; else dp[i][j] = dp[i-1][j]; } } printf("%lld\n",dp[n][goal]/2); } return 0; } /* */
下面是進行空間優化的寫法,思路完全一樣
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<list>
#include<map>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
ll dp[4500];
int main()
{
int n;
while( scanf("%d",&n) == 1 )
{
ll goal = (ll)( n*(n+1)/2);
if( goal & 1 )
{
puts("0");
continue;
}
goal = goal>>1;
memset(dp,0,sizeof dp);
dp[0] = 1;
for( int i = 1 ; i <= n ; i++ )
{
for( int j = goal ; j >= i ; j-- )
dp[j] = dp[j]+dp[j-i];
}
printf("%lld\n",dp[goal]/2);
}
return 0;
}