英文:
Dynamic programming with two parameters: days and coupons
问题
有一家咖啡馆有以下的折扣系统:每次购买金额超过100美元,买家将获得一张免费午餐的优惠券。
你有一个未来N天的价格列表,如下所示:
5
35
40
101
59
63
价格限制:0 ≤ 价格 ≤ 300
找出午餐的最小总成本以及应该使用优惠券的天数。
请帮忙完善DP(动态规划)的实现。我不确定初始的DP表是否适合这个任务,所以无法正确处理边界情况。
谢谢。
更新:扩展的解决方案似乎可以获得最佳结果,但未被第三方测试系统接受:
package main
import (
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"
)
func main() {
file, _ := os.ReadFile("input.txt")
lines := strings.Split(string(file), "\n")
p := make([]int, 0, len(lines))
for i := 1; i < len(lines); i++ {
price, err := strconv.Atoi(lines[i])
if err != nil {
continue
}
p = append(p, price)
}
l := len(p)
dp := make([][]int, l+1)
dp[0] = make([]int, len(dp))
for i := 1; i < len(dp); i++ {
dp[0][i] = math.MaxInt32
}
for i := 1; i < len(dp); i++ {
dp[i] = make([]int, l+1)
}
// 填充dp
for i := 1; i <= l; i++ {
for j := 0; j <= l; j++ {
if p[i-1] <= 100 {
if j == l {
dp[i][j] = MinOrZero(dp[i-1][j]+p[i-1], dp[i-1][j])
continue
}
dp[i][j] = MinOrZero(dp[i-1][j]+p[i-1], dp[i-1][j+1])
continue
}
if j == 0 {
dp[i][j] = MinOrZero(math.MaxInt32, dp[i-1][j+1])
continue
}
if j == l {
dp[i][j] = MinOrZero(dp[i-1][j-1]+p[i-1], dp[i-1][j])
continue
}
dp[i][j] = MinOrZero(dp[i-1][j-1]+p[i-1], dp[i-1][j+1])
}
}
// 剩余的优惠券
coupons := 0
min := math.MaxInt
for j := l; j >= 0; j-- {
sum := dp[l][j]
if sum < min {
min = sum
coupons = j
}
}
couponDays := make([]int, 0)
j := coupons
sum := min
for i := l; i > 0; i-- {
jl := j - 1
js := j
jr := j + 1
if jr < l && dp[i-1][jr] == sum {
couponDays = append(couponDays, i)
j++
continue
}
if dp[i-1][js] == sum {
sum = dp[i-1][js]
continue
}
if jl >= 0 && dp[i-1][jl] == sum-p[i-1] {
sum = dp[i-1][jl]
j--
continue
}
sum -= p[i-1]
}
sort.Ints(couponDays)
fmt.Println(min)
fmt.Println(fmt.Sprintf("%d %d", coupons, len(couponDays)))
for _, day := range couponDays {
fmt.Println(day)
}
}
func Min(i, j int) int {
if i <= j {
return i
}
return j
}
func MinOrZero(i, j int) int {
var result int
if i <= j {
result = i
} else {
result = j
}
if result >= 0 {
return result
}
return 0
}
英文:
There is a cafe that has the following discount system: for each purchase of more than $100, the buyer receives a coupon that gives the right to one free lunch.
You have a price list for the next N days like:
5
35
40
101
59
63
The price limitation: 0 ≤ price ≤ 300
Find the minimum possible total cost of lunches and the numbers of days on which you should use the coupons.
Please help to stabilize DP fulfilling. I am not sure the initial DP table is suitable for the task so I can not handle edge cases correctly:
func main() {
file, _ := os.ReadFile("input.txt")
lines := strings.Split(string(file), "\n")
p := make([]int, 0, len(lines))
for i := 0; i < len(lines); i++ {
price, err := strconv.Atoi(lines[i])
if err != nil {
continue
}
p = append(p, price)
}
dp := make([][]int, len(p)+1)
dp[0] = make([]int, len(dp))
// zero day row
for i := 1; i < len(dp); i++ {
// no coupons on the first day visit
dp[0][i] = math.MaxInt32
}
// 1...N days
for i := 1; i < len(dp); i++ {
dp[i] = make([]int, len(p))
}
for i := 1; i <= len(p); i++ {
for j := 0; j < len(p); j++ {
if p[i-1] <= 100 {
dp[i][j] = Min(dp[i-1][j]+p[i-1], dp[i-1][j+1])
}
dp[i][j] = Min(dp[i-1][j-1]+p[i-1], dp[i-1][j+1])
}
}
fmt.Println(dp)
}
func Min(i, j int) int {
if i <= j {
return i
}
return j
}
Thank you.
Upd. Extended solution which seems to achieve the best results but is unaccepted by 3rt party testing system:
package main
import (
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"
)
func main() {
file, _ := os.ReadFile("input.txt")
lines := strings.Split(string(file), "\n")
p := make([]int, 0, len(lines))
for i := 1; i < len(lines); i++ {
price, err := strconv.Atoi(lines[i])
if err != nil {
continue
}
p = append(p, price)
}
l := len(p)
dp := make([][]int, l+1)
dp[0] = make([]int, len(dp))
for i := 1; i < len(dp); i++ {
dp[0][i] = math.MaxInt32
}
for i := 1; i < len(dp); i++ {
dp[i] = make([]int, l+1)
}
// fill dp
for i := 1; i <= l; i++ {
for j := 0; j <= l; j++ {
if p[i-1] <= 100 {
if j == l {
dp[i][j] = MinOrZero(dp[i-1][j]+p[i-1], dp[i-1][j])
continue
}
dp[i][j] = MinOrZero(dp[i-1][j]+p[i-1], dp[i-1][j+1])
continue
}
if j == 0 {
dp[i][j] = MinOrZero(math.MaxInt32, dp[i-1][j+1])
continue
}
if j == l {
dp[i][j] = MinOrZero(dp[i-1][j-1]+p[i-1], dp[i-1][j])
continue
}
dp[i][j] = MinOrZero(dp[i-1][j-1]+p[i-1], dp[i-1][j+1])
}
}
// coupons left
coupons := 0
min := math.MaxInt
for j := l; j >= 0; j-- {
sum := dp[l][j]
if sum < min {
min = sum
coupons = j
}
}
couponDays := make([]int, 0)
j := coupons
sum := min
for i := l; i > 0; i-- {
jl := j - 1
js := j
jr := j + 1
if jr < l && dp[i-1][jr] == sum {
couponDays = append(couponDays, i)
j++
continue
}
if dp[i-1][js] == sum {
sum = dp[i-1][js]
continue
}
if jl >= 0 && dp[i-1][jl] == sum-p[i-1] {
sum = dp[i-1][jl]
j--
continue
}
sum -= p[i-1]
}
sort.Ints(couponDays)
fmt.Println(min)
fmt.Println(fmt.Sprintf("%d %d", coupons, len(couponDays)))
for _, day := range couponDays {
fmt.Println(day)
}
}
func Min(i, j int) int {
if i <= j {
return i
}
return j
}
func MinOrZero(i, j int) int {
var result int
if i <= j {
result = i
} else {
result = j
}
if result >= 0 {
return result
}
return 0
}
Input data test fails on. And the answer should be.
答案1
得分: 1
为了能够返回使用优惠券的日期,您需要跟踪最佳路径,然后在最后可以向后遍历。
当使用或获得车票时,您必须非常小心,因为它会改变dp数组中的位置。某些位置可能保持未定义。例如,如果您在第一天获得一张车票,那么对应于0张车票的单元格将保持未定义,因为当天的成本被放在1张车票上(如果价格超过100,第二天我们会得到0张或2张车票,但不是1张)。如果正确地进行上下跳跃,它应该可以工作。
以下是翻译好的代码部分:
function solve(prices) {
const dp = [[]], n = prices.length
let tickets = prices[0] > 100 ? 1 : 0
dp[0][tickets] = {cost: prices[0]}
for (let i = 1; i < n; i++) {
dp.push([])
let t = prices[i] > 100 ? 1 : 0
// not using tickets
for (let j = 0; j <= tickets; j++)
if (dp[i - 1][j])
dp[i][j + t] = {cost: dp[i - 1][j].cost + prices[i], prev: j}
// using tickets
for (let j = 1; j <= tickets; j++)
if (dp[i - 1][j] && (!dp[i][j - 1] || dp[i][j - 1].cost > dp[i - 1][j].cost))
dp[i][j - 1] = {cost: dp[i - 1][j].cost, prev: j}
tickets += t
}
// min has to be at 0 or 1 ticket
let idx = dp[n - 1][0] && (!dp[n - 1][1] || dp[n - 1][0].cost <= dp[n - 1][1].cost) ? 0 : 1
console.log('costs: ' + dp[n - 1][idx].cost)
const days = []
for (let day = n - 1; day > 0; day--) {
if (dp[day][idx].prev > idx)
days.unshift(day)
idx = dp[day][idx].prev
}
console.log('days: ' + days)
}
solve([88,55,101,77,44,130,160,22,55,97,101,2,88])
希望对您有所帮助!
英文:
To be able to return the days on which the coupons were used you need to keep track of the best path and then you can go backwards at the end.
You have to be very careful when tickets are used or gained because it shifts the position in the dp array. Some positions can stay undefined. E.g. if you get a ticket on the first day then the cell corresponding to 0 tickets stays undefined, because the costs for the day are put at 1 tickets (and if the price is above 100 the next day we end up with 0 or 2 tickets, but not with 1). If that jumping up and down is done correctly it should work.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function solve(prices) {
const dp = [[]], n = prices.length
let tickets = prices[0] > 100 ? 1 : 0
dp[0][tickets] = {cost: prices[0]}
for (let i = 1; i < n; i++) {
dp.push([])
let t = prices[i] > 100 ? 1 : 0
// not using tickets
for (let j = 0; j <= tickets; j++)
if (dp[i - 1][j])
dp[i][j + t] = {cost: dp[i - 1][j].cost + prices[i], prev: j}
// using tickets
for (let j = 1; j <= tickets; j++)
if (dp[i - 1][j] && (!dp[i][j - 1] || dp[i][j - 1].cost > dp[i - 1][j].cost))
dp[i][j - 1] = {cost: dp[i - 1][j].cost, prev: j}
tickets += t
}
// min has to be at 0 or 1 ticket
let idx = dp[n - 1][0] && (!dp[n - 1][1] || dp[n - 1][0].cost <= dp[n - 1][1].cost) ? 0 : 1
console.log('costs: ' + dp[n - 1][idx].cost)
const days = []
for (let day = n - 1; day > 0; day--) {
if (dp[day][idx].prev > idx)
days.unshift(day)
idx = dp[day][idx].prev
}
console.log('days: ' + days)
}
solve([88,55,101,77,44,130,160,22,55,97,101,2,88])
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论