百练的题目 : 让我对dp有了新体会 这是记忆化搜索写法 省了很多时间 并且状态转移的方式符合我们的认知
这个通过递推很难发现关系 但是讲区间搜索一下分解的话就可以计数了 并且在不影响结果的前提下自己规定了拿走数字的大小的方式和拿走的方向(能体会到这一点就好,这是写出来dp的关键 可以用复杂状态来表示准确的dp)
三段solve 分别对应 三个问题
<span style="font-family: Arial, Helvetica, sans-serif;">#include <stdio.h></span>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <iostream>
#include <sstream>
#include <ostream>
#include <algorithm>
#include <ctype.h>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define inf 1e9+7
#define pi acos(-1)
#define natrule exp(1)
using namespace std;
#pragma comment(linker, "/STACK:1024000000,1024000000")
int dp[55][55][55];
int dpp[55][55][55];
int dppp[55][55][55];
int arr[55];
int solve(int minn,int num,int k){
if(num<=0) return 0;
if(dp[num][k][minn]!=-1) return dp[num][k][minn];
if(num<k*minn) return 0;
if(num==k*minn) return 1;
if(k==1) return 1;
int sum=0;
for(int i=1;i<num;i++){
if(i>=minn){
sum+=solve(i,num-i,k-1);
}
}
return dp[num][k][minn]=sum;
}
int solve2(int minn,int num,int k){
if(num<=0) return 0;
if(dpp[num][k][minn]!=-1) return dpp[num][k][minn];
if(num<((minn+minn+minn+k-1)*k/2)) return 0;
if(num==k*minn) return 1;
if(k==1) return 1;
int sum=0;
for(int i=1;i<num;i++){
if(i>=minn){
sum+=solve(i+1,num-i,k-1);
}
}
return dpp[num][k][minn]=sum;
}
int solve3(int minn,int num,int k){
if(num<=0) return 0;
if(num%2!=k%2) return 0;
if(dppp[num][k][minn]!=-1) return dppp[num][k][minn];
if(num<k*minn) return 0;
if(num==k*minn) return 1;
int sum=0;
for(int i=1;i<num;i++){
if(arr[i]>=minn){
sum+=solve(arr[i],num-arr[i],k-1);
}
}
return dppp[num][k][minn]=sum;
}
int main()
{
memset(dp,-1,sizeof(dp));
memset(dpp,-1,sizeof(dpp));
memset(dppp,-1,sizeof(dppp));
for(int i=1;i<=50;i++) arr[i]=2*i-1;
int n;
cin>>n;
int k;
cin>>k;
cout<<solve(1,n,k)<<endl;
int sum=0;
for(int i=1;i<=n;i++){
sum+=solve2(1,n,i);
}
cout<<sum<<endl;
sum=0;
for(int i=1;i<=n;i++){
sum+=solve3(1,n,i);
}
cout<<sum<<endl;
return 0;
}
上面的代码正确 但是超时很严重 因为我的状态取得不好 我用 dp[i][j][min]来表示在把j分割成i个并且最小是i来做的 其实多了一维 空间虽然没爆但是时间妥妥的卡住了 ,这就要求我们优化一下dp的姿势 :
可以自行百度整数划分的资料 以下三种就是状态转移方程 天哪 因为写错了一个变量wa了一早上。。。。
复杂度可以接受 大家注意一点还可以优化 。。。 就是其实第二种和第三种实际上是一样的 但是怎么证明是一样的我还没想好
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <iostream>
#include <sstream>
#include <ostream>
#include <algorithm>
#include <ctype.h>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#include <vector>
#define inf 1e9+7
#define pi acos(-1)
#define natrule exp(1)
using namespace std;
#pragma comment(linker, "/STACK:1024000000,1024000000")
long long dp[55][55];
long long dpp[55][55];
long long dppp[55][55];
void solve1(int n,int k){
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][1]=1;
for(int i=1;i<=n;i++) {
for(int j=2;j<=i;j++){
dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}
}
}
void solve2(int n){
memset(dpp,0,sizeof(dpp));
for(int i=1;i<=n;i++) dpp[1][i]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=n;j++){
if(i<j) dpp[i][j]=dpp[i][i];
else if(i==j) dpp[i][j]=1+dpp[i][j-1];
else{
dpp[i][j]=dpp[i-j][j-1]+dpp[i][j-1];
}
}
}
}
void solve3(int n){
memset(dppp,0,sizeof(dppp));
for(int i=1;i<=n;i++) dppp[i][1]=1;
for(int i=1;i<=n;i++){
for(int j=2;j<=n;j++){
if(j&1) {
if(i<j) dppp[i][j]=dppp[i][i];
else if(i==j) dppp[i][j]=1+dppp[i][j-2];
else dppp[i][j]=dppp[i-j][j]+dppp[i][j-2];
}
else dppp[i][j]=dppp[i][j-1];
}
}
}
int main()
{
int n,k;
while(cin>>n>>k){
solve1(n,k);
cout<<dp[n][k]<<endl;
solve2(n);
cout<<dpp[n][n]<<endl;
solve3(n);
cout<<dppp[n][n]<<endl;
/*for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++){
cout<<dp[i][j]<<' ';
}
cout<<endl;
}
cout<<endl;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++){
cout<<dpp[i][j]<<' ';
}
cout<<endl;
}
cout<<endl;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++){
cout<<dppp[i][j]<<' ';
}
cout<<endl;
}*/
}
return 0;
}