zoukankan      html  css  js  c++  java
  • 整数划分问题(记忆化搜索和DP方法)

    一、 问题 现在有一正整数N,要把它分为若干正整数之和,问有多少种本质不同的分法?

    (1)其中最大数不超过m, 有多少种分法?

    (2)分割后的正整数的数目不超过m个, 有多少种分法?

    (3)分成最大数不超过m, 且每一个正整数都是正奇数, 有多少种分法?

    (4)分成最大数不超过m, 且每一个正整数都不同,有多少种分法?

    (5)分成恰好k个正整数,有多少种分法?

    二、分析

    (1)最大数不超过 m

      (a)设dp[i][j]表示把数字 i 分成最大数不超过  j 的若干正整数之和所得的方法数

      (b)有如下递推公式 ,核心是最大数m到底选还是不选

    (2)分割数不超过m个

      (a)设dp[i][j] 表示把数字 i 分成分割数不超过 j 的若干正整数之和所得的方法数

      (b)递推公式核心

    • 到底分不分成 j 个
      •   如果分成j个,则预先给每一份分 一个1,那么还剩下n-m,这n-m仍然继续分割成最多j个数;
      •   如果不分成j个,那就转移到了把 i 最多分成 j-1 个数的状态
    •  递推公式:

    • 发现竟然和(1)完全一样!其实(1)(2)问题是完全等价的
      •   从递推公式上来看,等价
      •   从图形来看等价:

    (3)分成最大数不超过m, 且每一个数都是正奇数

      (a)设dp[i][j] 表示把数字 i 分成分割数不超过 j 的若干正奇数之和所得的方法数

      (b)递推公式核心: 最大数 j 到底是不是奇数

    • 如果是奇数,那么 j 是可以作为 一种分法的元素的 转态转移到到底是分出 j 还是不分出 j
    • 如果不是奇数, 那么 j 是不能分出来的,状态转移到了dp[i][j-1]

    (4)分成最大数不超过m, 且每一个正整数都不同

      (a)设dp[i][j] 表示把数字 i 分成最大数不超过 j 的若干不同正整数之和所得的方法数

      (b)递推公式核心:当前最大数选还是不选,如果选,还剩下i-j并且下次的最大数不能再是j而是j-1, 如果不选,状态转移到dp[i][j-1]

     (5)分成恰好k个正整数

      (a)设dp[i][j] 表示把数字 i 分成 j 个正整数之和所得的方法数

      (b)递推公式核心

    • 这j个数里面含不含有1
      •   若不含1,则先为每份预分配一个1,再对i-j进行分割成j份;
      •   若含1,则先分出一个1, 然后再对剩下的的i-1分成 j-1份

    【记忆化搜索代码】

    #include<iostream>
    #include<queue>
    #include<list>
    #include<vector>
    #include<cstring>
    #include<set>
    #include<stack>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<algorithm>
    #include<string>
    #include<stdio.h>
    using namespace std;
    typedef long long ll;
    const double eps=1e-8;
    const double PI = acos(-1.0);
    const int inf = 0x3f3f3f3f;
    const ll INF = 0x7fffffff;
    #define MS(x,i) memset(x,i,sizeof(x))
    #define rep(i,s,e) for(int i=s; i<=e; i++)
    #define sc(a) scanf("%d",&a)
    #define scl(a) scanf("%lld",&a)
    #define sc2(a,b) scanf("%d %d", &a, &b)
    #define dubug printf("debug......
    ");
    const int maxn = 1e2+10;
    int dx[4] = {0, 0, 1, -1};
    int dy[4]  = {1, -1, 0 , 0};
    
    
    /*
    1. 把n划分成若干正整数之和 最大数不超过m 方法数
    2. 把n划分成不超过m个正整数之和 方法数
    1.2 等价
    */
    ll dp1[maxn][maxn];
    ll dp2[maxn][maxn];
    ll dp3[maxn][maxn];
    ll dp4[maxn][maxn];
    
    //把n划分成不超过m的若干正整数之和     把n划分成不超过m个正整数之和 方法数
    ll DP1(int n, int m){
        if(dp1[n][m] != -1){
            return dp1[n][m];
        }
        ll ans = 0;
        if(m == 1 || n == 1){
            return dp1[n][m] = 1;
        }
        if(m == n){
            return dp1[n][m] = DP1(n , m - 1) + 1;
        }
        if(m > n){
            return dp1[n][m] = DP1(n,n);
        }
        if(m < n){
            return dp1[n][m] = DP1(n-m, m) + DP1(n , m-1);
        }
        return dp1[n][m] = 0;
    }
    
    //把N分成不超过m的,若干个正奇数的和 的方法数
    ll DP2(int n , int m){
        if(dp2[n][m] != -1) return dp2[n][m];
    
        if(n == 1 || m == 1){
            return dp2[n][m] = 1;
        } 
        if(n == m){
            return dp2[n][m] = DP2(n , m-1) + m%2;
        }
        if(n < m){
            return dp2[n][m] = DP2(n , n);
        }
        if(n > m){
            if(m % 2){
                return dp2[n][m] = DP2(n-m , m) + DP2(n , m-1);
            }
            else{
                return dp2[n][m] = DP2(n , m - 1);
            }
        }
        return dp2[n][m] = 0;
    }
    
    //把N划分成不超过m的 不同正整数之和 的方法数
    ll DP3(int n, int m){
        if(dp3[n][m] != -1){
            return dp3[n][m];
        }
        if(n == 1) return dp3[n][m] = 1;
        if(m == 1 && n > 1){
            return dp3[n][m] = 0;
        }
    
        if(m == n){
            return dp3[n][m] = DP3(n , m - 1) + 1;
        }
        if(m > n){
            return dp3[n][m] = DP3(n,n);
        }
        if(m < n){
            return dp3[n][m] = DP3(n-m, m-1) + DP3(n , m-1);
        }
        return dp3[n][m] = 0;
    }
    
    //把n换分成恰好k个正整数之和
    ll DP4(int n, int k){
        if(dp4[n][k] != -1) return dp4[n][k];
        if(n == 1 && k > 1) return dp4[n][k] = 0;
        if(k == 1) return dp4[n][k] = 1;
        if(n == k) return dp4[n][k] = 1;
        if(n < k) return dp4[n][k] = 0;
        if(n > k) return dp4[n][k] = DP4(n-k , k) + DP4(n-1, k-1);
        return 0;
    }
    
    
    int n,m;
    int k;
    int main(){
        while(sc(n) != EOF){
            MS(dp1, -1);
            MS(dp2 , -1);
            MS(dp3 , -1);
            MS(dp4 , -1);
            sc(k);
            //printf("%lld
    ", DP1(n,n));
            printf("%lld
    ", DP4(n,k));
           // printf("%lld
    ", DP1(n,k));
            printf("%lld
    ", DP3(n,n));
             printf("%lld
    ", DP2(n,n));
            
        }
    
        return 0;
    }
    View Code

    【DP代码】

    #include<iostream>
    #include<queue>
    #include<list>
    #include<vector>
    #include<cstring>
    #include<set>
    #include<stack>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<algorithm>
    #include<string>
    #include<stdio.h>
    using namespace std;
    typedef long long ll;
    const double eps=1e-8;
    const double PI = acos(-1.0);
    const int inf = 0x3f3f3f3f;
    const ll INF = 0x7fffffff;
    #define MS(x,i) memset(x,i,sizeof(x))
    #define rep(i,s,e) for(int i=s; i<=e; i++)
    #define sc(a) scanf("%d",&a)
    #define scl(a) scanf("%lld",&a)
    #define sc2(a,b) scanf("%d %d", &a, &b)
    #define dubug printf("debug......
    ");
    const int maxn = 1e2+10;
    int dx[4] = {0, 0, 1, -1};
    int dy[4]  = {1, -1, 0 , 0};
    
    int n;
    int k;
    ll dp1[maxn][maxn];//把n划分成若干正整数之和且最大数不超过m    方法数   或者   把n划分成不超过m个正整数之和 方法数
    ll dp2[maxn][maxn];//把N分成不超过m的,若干个【正奇数】的和 的方法数
    ll dp3[maxn][maxn];//把N划分成不超过m的 【不同】正整数之和的方法数
    ll dp4[maxn][maxn];//把n换分成【恰好k个】正整数之和
    
    //把n划分成若干正整数之和且最大数【不超过m】    方法数   或者   把n划分成不超过【m个】正整数之和 方法数
    void DP1(){
        MS(dp1, 0);
        rep(i,1,n){
            rep(j,1,n){
                if(i == 1 || j == 1 ){
                    dp1[i][j] = 1;
                    continue;
                } 
                if(j < i)
                    dp1[i][j] = dp1[i-j][j] + dp1[i][j-1];
                else if(j > i)
                    dp1[i][j] = dp1[i][i];
                else
                    dp1[i][j] = dp1[i][j-1] + 1;
            }
        }
    }
    
    //把N分成不超过m的,若干个【正奇数】的和 的方法数
    void DP2(){
        MS(dp2, 0);
        rep(i,1,n){
            rep(j,1,n){
                if(i == 1 || j == 1){
                    dp2[i][j] = 1;
                    continue;
                }
                if(j < i){
                    if(j % 2)
                        dp2[i][j] = dp2[i-j][j] + dp2[i][j-1];
                    else{
                        dp2[i][j] = dp2[i][j-1];
                    }
                }
                else if(j == i){
                    dp2[i][j] = j%2 + dp2[i][j-1];
                }
                else
                    dp2[i][j] = dp2[i][i];
            }
        }
    }
    
    //把N划分成不超过m的 【不同】正整数之和的方法数
    void DP3(){
        MS(dp3, 0);
        rep(i,1,n){
            rep(j,1,n){
                if(i == 1 ){
                    dp3[i][j] = 1;
                    continue;
                } 
                if(j == 1 && i > 1){
                    dp3[i][j] = 0;
                    continue;
                } 
                if(i < j) dp3[i][j] = dp3[i][i];
                if(i == j ){
                    dp3[i][j] = 1 + dp3[i][j-1];
                }
                if(i > j){
                    dp3[i][j] = dp3[i-j][j-1] + dp3[i][j-1];
                }
            }
        }
       // cout<<dp3[5][5]<<endl;
    }
    
    //把n换分成【恰好k个】正整数之和
    void DP4(){
        MS(dp4 , 0);
        rep(i, 1, n) {
            rep(j , 1, n){
                // if(i == 1 && j > 1) {
                //     dp4[i][j] = 0;
                //     continue;
                // }
                if(j == 1) {
                    dp4[i][j] = 1;
                    continue;
                }
                if(i == j) dp4[i][j] = 1;
                if(i > j)
                    dp4[i][j] = dp4[i-j][j] + dp4[i-1][j-1];
                if(i < j)
                    dp4[i][j] = 0;
            }
        }
    }
    
    
    int main(){
        while(sc(n) != EOF){
            sc(k);
            DP1();
            DP2();
            DP3();
            DP4();
            cout<<dp4[n][k]<<endl;
            cout<<dp3[n][n]<<endl;
            cout<<dp2[n][n]<<endl;
        }
    
        return 0;
    }
    View Code
  • 相关阅读:
    python(三):python自动化测试定位
    python(二):用python抓取指定网页
    python(一):介绍网络爬虫
    oracle批量增加数据
    oracle数据库基本操作
    web安全测试要点
    linux下安装jdk、tomcat
    在linux上编译C
    linux基本操作
    对首次性能测试的总结与思考
  • 原文地址:https://www.cnblogs.com/czsharecode/p/10647413.html
Copyright © 2011-2022 走看看