[学習記録]AtCoder EDP M-Candies[Go]

問題

N 人の子供たちがいます。 子供たちには 1,2,…,N と番号が振られています。

子供たちは K 個の飴を分け合うことにしました。 このとき、各 i (1≤i≤N) について、子供 i が受け取る飴の個数は 0 以上 ai​ 以下でなければなりません。 また、飴が余ってはいけません。

子供たちが飴を分け合う方法は何通りでしょうか? 10^9+7 で割った余りを求めてください。 ただし、2 通りの方法が異なるとは、ある子供が存在し、その子供が受け取る飴の個数が異なることを言います。

解法

i人の子供で、j 個の飴を分け合うことを考える。
j 個の飴を分け合う組み合わせは i 番目の子供がとる数によって変わっていき、その数は i -1番目の子供たちが取った飴の数を、i 番目の子供が取る数を変えていったときの総和になる

// i=2, j=0,  (0,0)
// i=2, j=1,  (0,1)(1,0)
// i=2, j=2,  (1,1)(0,2)
// i=2, j=3,  (1,2)
// i=2, j=4,

// i=3, j=0,  (0,0,0)
// i=3, j=1,  (1,0,0)(0,1,0) | (0,0,1)
//  > i=3の子供が0ことるとき、組み合わせは i=2, j=1と同じになる
//  > (0,1,[0]),(1,0,[0])
//  > i=3の子供が1ことるとき、組み合わせは i=2, j=0と同じになる
//  > (0,0,[1])
//  >  2 + 1 = 3
// i=3, j=2,  (1,1,0) | (1,0,1)(0,1,1)
// i=3, j=3,  (1,2,0) | (1,1,1)(0,2,1) | (1,0,2)(0,1,2) | (0,0,3)
// i=3, j=4,  (1,2,1) | (0,2,2)(1,1,2) | (0,1,3)(1,0,3) |
dp := make([][]int, n+1)
dp[0] = make([]int, k+1)
dp[0][0] = 1

for i := 1; i <= n; i++ {
	dp[i] = make([]int, k+1)

	for j := 0; j <= k; j++ {
        // i 番目の子供が取る数を変えていったときの総和
		dp[i][j] = Σdp[i-1][x]
	}
}

ans := dp[n][k]

return ans

しかしながら、Σdp[i-1][x]をそのまま計算しようとすると、計算量が
O(NK^2)となり、TLEしてしまう。
そのため、累積和を取り、計算量を減らす。(O(NK))

sli := readSli(n)

for i := 1; i <= n; i++ {
	dp[i] = make([]int, k+1)
	cum := make([]int, k+2)

	cum[0] = 0
	for j := 1; j <= k+1; j++ {
		cum[j] = (cum[j-1] + dp[i-1][j-1])
	}

	for j := 0; j <= k; j++ {
		dp[i][j] = Σdp[i-1][x]
	}
}

Σの部分は、i 番目の人が何個受け取れるかに依存するのでそれに依存する
受け取れる個数( a ) が k より少ない場合、i 番目の人が受け取る個数は
k -a < j < k の間で変化し、これを累積和を用いて表すと
dp[i][j]   =    Σdp[i-1][x] = cum[ j + 1] - cum[ j - a ]         になる。
( j + 1 ・・・累積和のため、j まで足したときの和 = j+1となる)
累積和の開始インデックスがゼロを下回ることは無いため、Maxを取る


for i := 1; i <= n; i++ {
	dp[i] = make([]int, k+1)
	cum := make([]int, k+2)

	cum[0] = 0
	for j := 1; j <= k+1; j++ {
		cum[j] = (cum[j-1] + dp[i-1][j-1])
	}

	for j := 0; j <= k; j++ {
		dp[i][j] = cum[j+1] - cum[Max(0, j-sli[i-1])]
	}
}

dpのn, k が回答になる

コード(AC)

func run() interface{} {
	n, k := readInt(), readInt()

	sli := readSli(n)
	dp := make([][]int, n+1)
	dp[0] = make([]int, k+1)
	dp[0][0] = 1

	for i := 1; i <= n; i++ {
		dp[i] = make([]int, k+1)
		cum := make([]int, k+2)

		cum[0] = 0
		for j := 1; j <= k+1; j++ {
			cum[j] = (cum[j-1] + dp[i-1][j-1]) % prime_number
		}

		for j := 0; j <= k; j++ {
			dp[i][j] = (cum[j+1] - cum[Max(0, j-sli[i-1])] + prime_number) % prime_number
		}
	}

	ans := dp[n][k]

	return ans
}

この記事が気に入ったらサポートをしてみませんか?