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ビット配列に最適化することはできません:
- 行ごとに更新する場合、i番目の行にはi-1番目の行とij番目の行が必要であり、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;
}