整数划分问题模型
问题一: 求把n划分成k个正整数的方案数?
暴力dp:(暴力dp要做到不重不漏)
设dp[i][j][sum]表示前i个正整数,选了j个数,和为sum的方案数,答案是:dp[n][k][n]
本质:完全背包
复杂度:O(n*k*n)
正解:
直接设dp[i][j]表示把i划分成j个数的方案数
则dp[i][j]=dp[i-j][j]+dp[i-1][j-1](可从数形结合或者抽屉原理的方面来考虑)
问题二:求把n划分成互不相同k个正整数的方案数?
暴力dp:
设dp[i][j][sum]表示前i个正整数,选了j个数,和为sum的方案数,答案是:dp[n][k][n]
本质:0/1背包
复杂度:O(n*k*n)
正解:
直接设dp[i][j]表示把i划分成j个数的方案数
则dp[i][j]=dp[i-j][j]+dp[i-j][j-1](可从数形结合或者抽屉原理的方面来考虑)
问题三:求把n划分成k个不大于m的互不相同正整数的方案数?
直接上正解:
设dp[i][j]表示把i划分成j个数的方案数
dp[i][j]=dp[i-j][j]+dp[i-j][j-1]-dp[i-(m+1)][j-1(可从数形结合或者抽屉原理的方面来考虑)
问题四:求把n划分成k个奇数的方案数?
引用zhhx的:
可重复数字纯奇纯偶划分
g[i][j]:将i划分为j个偶数的方案数
f[i][j]:将i划分为j个奇数的方案数
g[i][j] = f[i - j][j] f[i][j] = f[i - 1][j - 1] + g[i - j][j];
不可重复也是一样的。
例题:
洛谷P1025 数的划分
问题一“把n划分成k个正整数的方案数”的弱化版,好像是暴力dp就能过的那种,但可以拿来练手,判判边界之类的(水经验)
#include <iostream>
#include <cstdio>
using namespace std;
int f[210][10];
int n,k;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)f[i][1]=1;
for(int i=1;i<=k;i++)f[i][i]=1;
for(int i=2;i<=n;i++)
{
for(int j=2;j<=k&&j<i;j++)
{
f[i][j]=f[i-1][j-1]+f[i-j][j];
}
}
printf("%d",f[n][k]);
return 0;
}
洛谷P4104 [HEOI2014]平衡
问题可转化为从1~2n+1中选出k个整数,使得这k个整数和为k*(n+1)
其实就是问题三“把n划分成k个不大于m的互不相同正整数的方案数”的模板
在原问题中应该是把k*(n+1)划分成k个不大于2n+1的互不相同的正整数的方案数
复杂度:O(n*k*k)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,k,p;
int f[100100*2][15];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
memset(f,0,sizeof(f));
scanf("%d%d%d",&n,&k,&p);
for(int i=1;i<=(2*n+1);i++)f[i][1]=1;
for(int i=2;i<=k*(n+1);i++)
{
for(int j=2;j<=k&&j<i;j++)
{
int m=2*n+1;
if(i>(m+1))
f[i][j]=f[i-j][j-1]%p+f[i-j][j]%p-f[i-(m+1)][j-1]%p;
else f[i][j]=f[i-j][j-1]%p+f[i-j][j]%p;
}
}
printf("%d
",(f[k*(n+1)][k]%p+p)%p);
}
return 0;
}