复杂的正整数划分

Posted by 许大仙's Litt1ε Bl0g on August 22, 2017

对于简单的正整数划分只需要divide(n,m),把n数做m的划分,即n划分成的k份中,每份的值都小于m. 可以参考ACM题解——正整数划分

#复杂的正整数划分 简单的正整数划分是针对n个数划分的所有可能。那么如果题目有要求划分的条件,那么状态转移方程将有所不同。

可能出现的划分要求有:

  • 将n划分成若干正整数之和的划分数。(简单正整数划分)
  • 将n划分成k个正整数之和的划分数。
  • 将n划分成最大数不超过k的划分数。
  • 将n划分成若干奇正整数之和的划分数。
  • 将n划分成若干不同整数之和的划分数。

不同情况下的状态转移方程

1.将n划分成不大于m的划分法(对划分每份的上限的要求做分类,状态量是需要划分的数以及每份数值上限)

1).若是划分多个整数可以存在相同的:
	dp[n][m]= dp[n][m-1]+ dp[n-m][m]  

	含义:dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。

	划分数可以分为两种情况:
		a.划分中每个数都小于 m,相当于每个数不大于 m- 1, 故划分数为 dp[n][m-1].
		b.划分中有一个数为 m. 那就在 n中减去 m ,剩下的就相当于把 n-m 进行划分, 故划分数为 dp[n-m][m];
		(因为划分的整数可以相同,所以n-m后划分的最大上限还是m)

2).若是划分多个不同的整数:
	dp[n][m]= dp[n][m-1]+ dp[n-m][m-1]   

	含义:dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。

	划分情况分为两种情况:
		a.划分中每个数都小于m,相当于每个数不大于 m-1,划分数为 dp[n][m-1].
		b.划分中有一个数为 m.在n中减去m,剩下相当对n-m进行划分,并且每一个数不大于m-1,故划分数为 dp[n-m][m-1].
		(由于划分出的每份整数要求不同,所以n-m划分的最大上限是m-1)

2.将n划分成k个数的划分法:(对划分分数做分类,状态量是需要划分的数以及划分的份数)

dp[n][k]= dp[n-k][k]+ dp[n-1][k-1];

含义:dp[n][k],表示整数n的划分中,需要划分k份

方法可以分为两类:(分出的k份中是否包含值为1的份)
	第一类: n 份中不包含 1 的分法。
			为保证每份都 >= 2,可以先拿出 k 个 1 分。到每一份,然后再把剩下的 n- k 分成 k 份即可,分法有: dp[n-k][k]
			(剩下n-k的值还要再分k分和原来拿出的k份1合并,使得每份必>1(即>=2))
	第二类: n 份中至少有一份为 1 的分法。
			可以先拿出一个 1 作为单独的1份,剩下的 n- 1 再分成 k- 1 份即可,分法有:dp[n-1][k-1]
	(已经分出完成的一份1了,剩下n-1的值还需要分k-1份就可以)

3.将n划分成若干奇数的划分法:(计算奇数划分时候同时考虑偶数,状态量为需要划分的数和划分的份数)

方法一:

g[i][j] = f[i - j][j]; 
f[i][j] = f[i - 1][j - 1] + g[i - j][j];

含义:g[i][j]:将i划分为j个偶数
	f[i][j]:将i划分为j个奇数

方法可以分为两个步骤:
  第一步:g[i][j] = f[i - j][j]; 
		i中拿出j个1分到每一份中,将剩余的i-j分成j个奇数
		(这j个奇数和原来拿出的1变成j份偶数。分i为j个偶数(用g表示分偶数)=分i为1+j个奇数=分i-j为j个奇数(用f表示分奇数))
		
  第二步:将i划分为j个奇数,等同于两种子问题的和
		a.j份中包含有一份为1的情况:一份包含奇数1,剩余的i-1分成j-1个奇数(f表示分成奇数值);
		b.j份中不包含一份为1的情况:每份至少大于1,将j个1拿出来分到每一份中,其余i-j分成j份的偶数(j份的偶数和原来j份的1形成j份奇数f[i][j])

方法二:

含义:f[i][j]表示数i划分成j个正奇数的方案数

初始值:
对于i为奇数  f[i][1]=1;f[i][2]=0;
对于i为偶数   f[i][1]=0;f[i][2]=(i/2+1)/2;

考虑f[i][j],需满足i>=j,j>2时
若最小的正奇数是1即j份中包含有一份为1的情况。
对应的方案数是 f[i-1][j-1]
若最小的正奇数>1即j份中不包含一份为1的情况。
从每个正整数中抽调2,对应的方案数f[i-2*j][j];
(为什么要抽2个给每一份,而不是1个,原因是抽两个后还是把i-2*j的值分成j份的奇数值。如果凑1个给每份,就要引入分成j份的偶数值,也就是方法一中的g的含义)

状态转移方程:f[i][j]=f[i-1][j-1]+f[i-2*j][j];

对应代码如下:

        for (int i=1;i<=n;i++){
			if (i%2) 
            {
              f[i][1]=1;
              f[i][2]=0;
            }
        else
          {
            f[i][1]=0;
            f[i][2]=(i/2+1)/2;
          }
		}
        for (int i=3;i<=n;i++){
          for (int j=1;j<=i;j++)
            {
              f[i][j]=f[i-1][j-1];
              if (i-2*j>=j)
                f[i][j]+=f[i-2*j][j];
            }
		}
        for (int i=1;i<=n;i++)
          ans+=f[n][i];
        cout<<ans<<endl;//输出所有i分奇数组合的可能

		//如果是i分成k份奇数值,那么直接输出F[i][k]
       }

类似题目也可以考虑用母函数来解决。