[カウントdp] | AcWingアルゴリズムの基本クラステストの質問の概要

900.整数除算

タイトル説明

正の整数nは、いくつかの正の整数の合計として、n = n1 + n2 + ... + nkの形式で表すことができます。ここで、n1≥n2≥...≥nk、k≥1です。

このような表現を正の整数nの除算と呼びます。(無秩序、再現可能)

正の整数nが与えられた場合、nにはいくつの異なる除算方法があるかを調べてください。

入力形式は
1行で、整数nが含まれています。

出力形式は
、除算の総数を表す整数を含む1行です。

答えは非常に大きい可能性があるため、出力結果には10 9 + 7を法としてください

データ範囲
1≤n≤1000

入力サンプル:

5

サンプル出力:

7

アイデア1:完全なバックパック思考

アイデア:整数1、2、3、…nをn個のオブジェクトの体積と見なします。これらのn個のオブジェクトのいずれも、使用できる回数に制限はありません。バックパックを埋めることができるソリューションの総数を尋ねてください。総体積n(完全なナップサック問題)変形)

状態表現: f[i][j]最初のi個の整数(1,2 ...、i)が整数jを構成することを意味するスキーム数。
スキームの数を見つけます:0 i、1 i、2 i、..を選択します。 、セットからSIかつ、全てのiを加えるs*i<=j,(s+1)*i>j
f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2*i] + ...+ f[i][j - s*i] ;
f[i][j - i] =      f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...+ f[i][j - s*i];
ことである最適化された状態遷移式- [J] + F [I]、[J - I] F [I] [J] = F [1 I]。 F [I] [J] = F [i-1] [j] + f [i] [j-i];f [ i ] [ j ]=f [ i 1 ] [ j ]+f [ i ] [ j i ] ;

初期化の問題:
i-1とjiが存在するため、行0と列0が関係している可能性があります。
そして、番号1、つまり最初の行から再帰的に開始します。0番目の行の初期化を使用する必要があります。
f[0][j]つまり、0番目の行では、番号を選択せず​​に番号jを選択することは明らかに不可能です。f[0][0]初期化を除いて、すべてが0に初期化されるため、何も選択せずに数値0を取得できますf[0][0]=1
f[i][0]i個の数に0を合わせると、何も選択されないので、f[i][0]=1

そして、0番目の行を初期化するだけでよいので、以下の計算では、jが0からループを開始し、各行を使用できるようにしますf[i][0]=1

#include <iostream>

using namespace std;

const int N = 1e3 + 7, mod = 1e9 + 7;

int f[N][N];

int main() {
    
    
    int n;
    cin >> n;

    f[0][0] = 1; "容量为0时,前 i 个物品全不选也是一种方案,凑够数字0,将第0行的f[0][0]初始化为0"
    
    for (int i = 1; i <= n; i ++) {
    
    
        for (int j = 0; j <= n; j ++) {
    
     "j从0开始"
            f[i][j] = f[i - 1][j]; "同时处理j=0的列"
            if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
        }
    }
    cout << f[n][n] << endl;
    return 0;
}

一次元アレイの最適化:
その観察Fを[I] [J] = F [I - 1]〜[J] + F [I] [J - I]、F [i]は[J] = F [I-1] [j] + f [i] [j-i];f [ i ] [ j ]=f [ i 1 ] [ j ]+f [ i ] [ j i ] ;

行ごとに更新する場合、つまり、最初に行を循環させ、次に列を循環させる場合、最初のi番号の下にあるさまざまな番号jのさまざまなスキームの数を見つけます。次に、i番目の行とi-1番目の行のみを使用できます。 1次元配列ストレージとして最適化できます。以前に更新されたものは後の方によって使用されるため正の順序でトラバース
ます。

アルゴリズムの実装

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1010,mod=1e9+7;
int dp[N];

int main() {
    
    
    int n;
    read(n);
 "初始化第0行"
    dp[0]=1;
    
 "每次更新一行,先循环i,再循环j,保证可以得到需要的数据"
    for (int i=1;i<=n;i++)
        for (int j=i;j<=n;j++)  "正序遍历"
        	dp[j]=(dp[j]+dp[j-i])%mod;
    
    printf("%d",dp[n]);    
    
    return 0;
}

アイデア2:コレクションを人為的に分割する

f[i][j]表しJ分割Iの、ある合計はIであり、そして数iは、数で示されるスキームJにより番号。表示される数の範囲は1からnです。
j個の数のセットを人為的に2つの部分に分割します:それは数1を含んでいますか?含まれている場合は、1をキックアウトできます。含まれていない場合は、コレクション全体を1つ減らすことができます。

動的伝達方程式f[i][j]=f[i-1][j-1]+f[i-j][j];

最終結果:プログラム番号nは、番号j、j = 1,2、......、nの合計で表され、とf[n][1]f[n][n]累積する必要があります。

初期化の問題:、
f[0][j] jの正の数は数0を表し、f[i][0]0の正の数は数iを表します。0の正の数は数0を表すことができるため
最初の行と最初の列f[0][0]f[0][0]=1を除いてすべて0に初期化されます。

2次元テーブルfの場合、主対角線のf[i][i]i数はi数を示します。これはすべて1でなければならず、ケースは1つしかないためf[i][i]=1です。
       最初の列f[i][1]では、数iは1つの数で表されf[i][1]=1ます。これは間違いなくiなので、です。

さらに、最終的な2次元テーブルfは下三角行列です。これは、数iの場合、jの数が加算され、jの最大値がiであるためです。i1を追加します。

1ビット配列に最適化することはできません:

  1. ごとに更新する場合、i番目のにはi-1番目の行とij番目の行が必要であり、2つの行は使用されません。
  2. 列で更新する場合、j番目の列を探す場合、j列とj-1列のみが使用されますが、最終的に2次元テーブルのn行目が必要になります。列で更新する場合、j番目の列のみが記録されます。私たちが望む最終結果ではありません。

アルゴリズムの実装

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1010,mod=1e9+7;
int dp[N][N];

int main()
{
    
    
    int n;
    read(n);
    //初始化
    dp[0][0]=1;
    
    for (int i=1;i<=n;i++)
        for (int j=1;j<=i;j++)  
            dp[i][j]=(dp[i-1][j-1]+dp[i-j][j])%mod;
    
    int res=0;
    for (int i=1;i<=n;i++) res=(res+dp[n][i])%mod;
    printf("%d",res);    
    
    return 0;
}

アイデア3:分割統治

状態:数nが最大でm個の部分に分割され、数nがm個の数に加算されることを
f[n][m]意味します。

LL solve(int n , int m ){
    
      
    "如果n == 1    显然只能分成一组 "
    "如果m == 1    显然只能分成一组 "
    if( n == 1 || m == 1)    return 1 ;

    "如果n > m "
    "分成m份:那么给集合中每个数减去1,此时份数不变,等价于=> solve(n-m,m)"
    "不分成m份: 递归求分成m-1份=>solve(n,m-1)"
    if( n > m ) return (solve(n-m,m)+solve(n,m-1))%mod;

    "如果n == m "
    "情况同上,n > m,只不过分成n份时结果已知为1,就不用再去减1了, 不分成m份时=>solve(n,m-1)"
    if(n == m ) return  (1+solve(n,m-1))%mod ;
    
    "如果n < m , solve(n,m) == solve(n,n)"
    if(n < m )  return solve(n,n)%mod ; 
}

C ++コード

#include <iostream>

using namespace std;

const int N = 1010,mod = 1e9+7;

int f[N][N];

int main()
{
    
    
    int n; 
    cin >> n; 

    for(int i = 1 ; i <= n ; i++)   f[1][i] = f[i][1] = 1;     "第一行第一列才都初始化为1"

    for(int i = 2 ; i <= n ; i++ ){
    
    
        for(int j = 2; j <= n ; j++ ){
    
      "没有限制j<=i,所以上面第一行第一列才都初始化为1"
            if(i == j)  f[i][j] = (1+f[i][j-1])%mod ;
            else if(i > j)   f[i][j] = (f[i-j][j]+f[i][j-1])%mod ;
            else    f[i][j] = f[i][i]%mod; 
        }
    }

    cout << f[n][n] << endl;

    return 0;
}

おすすめ

転載: blog.csdn.net/HangHug_L/article/details/114437100