整数的划分 动态规划
题目描述
每个非负整数都可以被拆分,比如说
2 = 2
2 = 1+1
3 = 3
3 = 2+1
3 = 1+1+1
输入格式 一个非负整数\(n(0 \leq n \leq 100)\)
输出格式 输出可以被拆分的方案数
输入样例
4
输出样例
5
样例解释
4 = 4
4 = 3+1
4 = 2+2
4 = 2+1+1
4 = 1+1+1+1
共5种
本题是我在学 \(dp\) 时候做的一道题
40分解法
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define R register
int n,ans;
int vis[500005];
void dfs(R int x,R int y)
{
if(x<0)return;//拆过头了
if(!x)//没法拆了
{
++ans;
return;
}
for(R int i=vis[y-1]; i<=x; i++)//要比上次拆的大
{
//这种拆法是反过来拆的,本质上一样
//每一个拆分出的数字都记录在vis数组中
//例如 4 = 1+1+2
if(i<n)
{
vis[y]=i;
dfs(x-i,y+1);
}
}
}
signed main()
{
scanf("%lld",&n);
if(!n)return puts("1"),0;//0就一种
vis[0]=1;//最小拆个1出来
dfs(n,1);
printf("%lld\n",ans+1);//不拆也是一种
return 0;
}
如果认为暴力能过,那请看以下数据
输入: 100
输出: 190569292
综上所述,这道题正解是\(dp\)
100分 解法一
设 \(dp[i][j]\) 表示 \(i\) 拆分成 \(j\) 个数的方案数
我们可以把 \(j\) 看作 \(j\) 个桶,\(i\) 就是 \(i\) 个\(1\)
1.\(dp[i-1][j-1]\) 一个桶里已经放了一个 \(1\) 了,还有 \(i-1\) 个 \(1\) 要划分到 \(j-1\) 个桶考虑\(i-1\)的\(j-1\)划分(\(1\)本身作为一个划分)保证划分中包含\(1\)
- \(dp[i-j][j]\) 先在每一个桶里都放个 \(1\),还有 \(i-j\) 个 \(1\) 要划分到这 \(j\) 个桶里(\(i-j\)不作为单独作为划分,因此还是划分为\(j\)组),考虑 \(i-j\) 的 \(j\) 划分,在合法的情况下保证划分中不包含\(1\) \([\) 对于 \(i\) 的 \(j\) 划分\(a_k\)( \(\sum\limits_{k=1}^{j}{a_k}=i,a_k\in \mathbb{Z}_+\) ),都有 \(a_k>0\) ,因此 \(a_k-1\) 对应了 \(i-j\) 的 \(j\) 划分 \(]\)
所以状态转移方程就是
\(dp[i][j]=dp[i-j][j]+dp[i-1][j-1]\)
注:对于当前状态的划分中包含 \(1\) (\(dp[i-1][j-1]\)),不保证状态转移后仍有 \(1\)
例如 \(dp[7][3]\) 的一种划分方式 \(1+2+4\)
\(dp[7][3]\) \(\xrightarrow{}\) \(1+dp[6][2]\) \(\xrightarrow{}\) \(1+\frac{dp[4][2]}{+1}\) \(\xrightarrow{}\) \(1+\frac{1+dp[3][1]}{+1}\) \(\xrightarrow{}\) \(1+\frac{1}{+1}+\frac{3}{+1}\) \(\xrightarrow{}\) \(1+2+4\)
边界:
\(0\) 的总方案数为 \(1\)
\(j=1\) ,方案数就是 \(1\)
\(i<j\) 时无法划分成 \(j\) 个数,方案数为 \(0\)
\(i=j\) 时方案数为 \(1\)
最后答案就是 \(\sum\limits_{i=1}^{n}{dp[n][i]}\)
时间复杂度 \(O(n^2)\)
空间复杂度 \(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define R register
int n,ans;
int dp[105][105];
signed main()
{
scanf("%lld",&n);
if(!n)return printf("1\n"),0;
for(R int i=1; i<=n; i++)
for(R int j=1; j<=n; j++)
if(i==j||j==1)dp[i][j]=1;
else if(i>j) dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
for(R int i=1; i<=n; i++)
ans+=dp[n][i];
printf("%lld\n",ans);
return 0;
}
100分 解法二
设 \(dp[j]\) 为 \(j\) 的所有划分方案数
对于 \(\forall j \in \mathbb{Z}_+\),\(dp[j]=\sum\limits_{i=1}^{j}{dp[j-i]}\)
边界: \(0\) 的方案数为 \(1\)
时间复杂度 \(O(n^2)\)
空间复杂度 \(O(n)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define R register
int n,dp[105]={1};
signed main()
{
scanf("%lld",&n);
for(R int i=1; i<=n; i++)
for(R int j=i; j<=n; j++)
dp[j]+=dp[j-i];
printf("%lld\n",dp[n]);
return 0;
}