1. 程式人生 > >Openjudge 百練 03:複雜的整數劃分問題

Openjudge 百練 03:複雜的整數劃分問題

03:複雜的整數劃分問題

總時間限制: 
200ms 
記憶體限制: 
65536kB
描述

將正整數表示成一系列正整數之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 k>=1 正整數的這種表示稱為正整數的劃分。

輸入
標準的輸入包含若干組測試資料。每組測試資料是一行輸入資料,包括兩個整數N 和 K。 
(0 < N <= 50, 0 < K <= N)
輸出
對於每組測試資料,輸出以下三行資料:
第一行: N劃分成K個正整數之和的劃分數目
第二行: N劃分成若干個不同正整數之和的劃分數目
第三行: N劃分成若干個奇正整數之和的劃分數目
樣例輸入
5 2
樣例輸出
2
3
3

這道題有三個問題,一是有個數限制的整數劃分,二是不同數的劃分,三是劃分成奇數
在考慮有限個數整數劃分時,我們可以設狀態dp[i][j],表示將整數i劃分成j個整數

那麼就有dp[i][j]=dp[i-j][j]+dp[i-1][j-1]。

dp[i-j][j]有j個整數(都不為1),每個整數都同時減1。

dp[i-1][j-1]表示當前構成i的整數中有1,去掉裡面的1。

至於劃分為不重複整數和奇數,既可以列舉整數或奇數做0-1揹包,也可以通過第一個問題的方法轉移 dp[i][j]=dp[i-j][j-1]+dp[i][j-1]; | j<=i;(上限合適)->劃分成的數裡有j+劃分成的數裡沒有j


dp[i][j]=dp[i][i];                        | j>i (上限過大)

只是奇數做DP時要注意列舉情況
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

#define MAXN 50
#define MAXM
#define INF 0x3f3f3f3f
typedef long long int LL;

int N,K;
LL dp[MAXN+10][MAXN+10];

void work1()//選K個
{
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=N;++i)//將i分成1個數只有一種方案
		dp[i][1]=1;
	
	for(int i=1;i<=N;++i)
        for(int j=2;j<=i;++j)//將每個數統一減1,或去掉當前數中的1
			dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
	
	printf("%d\n",dp[N][K]);//把N分成K個數
}

void work2()//任意不同
{
	memset(dp,0,sizeof(dp));  
	dp[0][0]=1;  
	
	for(int i=0;i<=N;i++)
	{
        for(int j=1;j<=N;j++)
		{					//當前有數是j和降低上限
			if(j<=i)dp[i][j]=dp[i-j][j-1]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//上限應為i
		}
	}
	
	printf("%lld\n",dp[N][N]);//劃分N,上限為N
}

void work3()//任意奇數(基本同work1)
{
	memset(dp,0,sizeof(dp));
	for(int i=0;i<=N;++i)
	{
		dp[i][1]=1;		
		if(i&1)dp[0][i]=1;//預處理第0層
	}
	
	for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=N;j++)
		{
			if(j&1)//同work1
			{
				if(j<=i)dp[i][j]=dp[i-j][j]+dp[i][j-1];
				else dp[i][j]=dp[i][i];
			}
			else dp[i][j]=dp[i][j-1];//當前非奇數
		}
	}
	
	printf("%lld\n",dp[N][N]);
}

int main()
{
	while(~scanf("%d%d",&N,&K))
	{
		work1();
		work2();
		work3();
	}
	
}