1371.通貨システム
与えられたVVV通貨(単位:人民元)、各通貨の使用回数に制限はありません。
異なる通貨は同じ額面金額を持つ場合があります。
さて、このVVを使ってほしいV通貨はNNを構成しますN元、いくつかの異なる方法がありますか?
入力フォーマット
最初の行には2つの整数VVが含まれていますVとNNN。
次の数行で、合計VVが入力されますV整数、各整数は通貨の額面を表します。
出力形式
要求されたソリューションの総数を表す整数を出力します。
データ範囲
1≤V≤251≤V≤251≤V≤2 5、
1≤N≤100001≤N≤100001≤N≤1 0 0 0 0
答えはであることが保証されている長い長いですl o n g long long範囲内のL個のO 、N 、G。
入力サンプル:
3 10
1 2 5
サンプル出力:
10
アイデア:DP分析
ステータス表示: f[i][j]
前のi
通貨から選択されたj
スキームの数を示し、その合計値は選択されたすべてのメソッドのセットを超えません。
次にf[n][m]
、前のn
通貨から選択され、合計値が超えないm
すべてのオプションのセット内のオプションの数が答えであることを意味します。
セット分割:
セクションに従って、i
通貨は0
、1
a、2
a、3
a ,,,,k
、セグメント化されたコレクションの数を選択できますf[i][j]
。その中でk*w[i] <= j
、つまり、バックパックをロードできる場合、列挙された最初のi
通貨はいくつかを選択できます。
状態計算:
f[i][j] = f[i-1][j]+f[i-1][j-w[i]]+f[i-1][j-2*w[i]],,,,,,+f[i-1][j-k*w[i]]
Javaコード
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[][] dp = new long[n + 1][m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0][0] = 1;
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
for(int k = 0; k*v <= j; k++){
//当前货币使用0~k次
dp[i][j] += dp[i-1][j-k*v];
}
}
}
System.out.println(dp[n][m]);
}
}
最適化を検討する
v
最初のi
記事のボリュームを表します(額面)
前のi
通貨から選択すると、値を超えないj
状態は次のように表されます。
f[i][j] = f[i-1][j] + f[i-1][j-v]+f[i-1][j-2v]+,,,,,+f[i-1][j-kv])
前のi
通貨から選択するとj-v
、値を超えない状態は次のように表されます。
f[i][j-v] = f[i-1][j-v]+f[i-1][j-2v]+,,,,,+f[i-1][j-kv])
次の2つのタイプを減算します。
f[i][j] = f[i-1][j]+f[i][j-v])
Javaコード
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[][] dp = new long[n + 1][m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0][0] = 1;
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
dp[i][j] = dp[i-1][j];
if(j >= v) dp[i][j] += dp[i][j-v];
}
}
System.out.println(dp[n][m]);
}
}
最適化するために、継続検討と一次元DPになる
上から得られたf[i][j] = f[i-1][j]+f[i][j-v])
。我々が発見f[i][j]
アウターことfor
ループはi
値最初のループ、および外側の値だけfor
ループはi-1
値最初のように、ループ同等の変換により、上記の式は次のようになります。
f[j] = f[j] + f[j-v]
ではf[j] = f[j] + f[j-v]
、なぜそれはf[i][j] = f[i-1][j]+f[i][j-v])
等しいのですか?
- 等号の右側
f[j]
は、左側f[j]
の現在のfor
ループを更新するために使用されます。現在の外側のループが最初のi
ループであるのに対し、等号の右側はf[j]
前のfor
ループ、つまり外側のfor
ループで計算されることに注意してください。がi - 1
計算され、時間内f[j]
に保存されf[i-1][j]
ます。つまり、です。 - 等号の右側にある
f[j-v]
のfor
は、内側のループで小さいものから大きいものに更新するためであり、それj-v
よりも小さくする必要があるj
ため、外側のループがシークするf[j]
前の最初のループであったときの値にf[j-v]
計算および更新されています。 、つまり。では、なぜ上記が同等なのですか。for
i
f[i][j-v]
f[j] = f[j] + f[j-v]
f[i][j] = f[i-1][j]+f[i][j-v])
Javaコード
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//用n种货币凑成m元钱的方案数,方案数可能很大,故用long型
long[] dp = new long[m + 1];
//初始化,用0种货币凑成0元钱的方案数是1,其余dp[0][j]默认是0
//即用0种货币凑成j(j!=0)元钱的方案数是0
dp[0] = 1;
/*for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = 0;j <= m;j++){
//内层for循环中就一个if语句,我们发现if语句中的条件还可以提到for中,故还可简化为下面的写法
if(j >= v) dp[j] += dp[j-v];
}*/
for(int i = 1;i <= n;i++){
int v = scanner.nextInt();//当前货币
for(int j = v;j <= m;j++){
dp[j] += dp[j-v];
}
}
System.out.println(dp[m]);
}
}