dp 计数问题 复杂整数划分 区间dp

12 篇文章 0 订阅

百练的题目 : 让我对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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值