You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双参数动态规划求助:餐厅优惠券最优方案调试

问题描述

餐厅折扣规则:单次消费超过100美元可获得1张免费午餐优惠券。给定未来N天的午餐价格列表(示例输入:

5
35
40
101
59
63

),价格范围为0≤price≤300。需求是计算午餐的最小总成本,以及使用优惠券的日期。

本人无法正确处理边缘情况,不确定初始DP表是否适配需求,请求协助实现稳定的DP方案。

初始实现代码
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
}
扩展后的实现代码
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
}
测试失败用例
  • 输入数据:
10
101
101
101
101
101
101
101
101
101
101
  • 预期输出:
505
5 5
2
4
6
8
10

说明:10天每天消费101美元,每消费一次得1张优惠券,最优策略是用优惠券抵消偶数天的费用,总成本为5*101=505,剩余5张优惠券,使用优惠券的日期为第2、4、6、8、10天。

修正后的稳定DP实现
package main

import (
	"fmt"
	"math"
	"os"
	"sort"
	"strconv"
	"strings"
)

func main() {
	file, _ := os.ReadFile("input.txt")
	lines := strings.Split(string(file), "\n")

	var p []int
	for _, line := range lines {
		price, err := strconv.Atoi(line)
		if err != nil {
			continue
		}
		p = append(p, price)
	}
	n := len(p)
	if n == 0 {
		fmt.Println(0)
		fmt.Println("0 0")
		return
	}

	// dp[i][j]:处理完前i天,剩余j张优惠券的最小总成本
	maxCoupons := n
	dp := make([][]int, n+1)
	for i := range dp {
		dp[i] = make([]int, maxCoupons+1)
		for j := range dp[i] {
			dp[i][j] = math.MaxInt32
		}
	}
	dp[0][0] = 0 // 初始状态:0天,0优惠券,成本0

	for i := 1; i <= n; i++ {
		price := p[i-1]
		for j := 0; j <= maxCoupons; j++ {
			// 情况1:付费当天的午餐
			if dp[i-1][j] != math.MaxInt32 {
				newCost := dp[i-1][j] + price
				newJ := j
				if price > 100 {
					newJ += 1
					if newJ > maxCoupons {
						newJ = maxCoupons
					}
				}
				if newCost < dp[i][newJ] {
					dp[i][newJ] = newCost
				}
			}

			// 情况2:使用优惠券抵消当天的午餐(仅当有优惠券时)
			if j > 0 && dp[i-1][j-1] != math.MaxInt32 {
				if dp[i-1][j-1] < dp[i][j] {
					dp[i][j] = dp[i-1][j-1]
				}
			}
		}
	}

	// 找到最小总成本,以及对应的剩余优惠券数量
	minTotal := math.MaxInt32
	remainingCoupons := 0
	for j := 0; j <= maxCoupons; j++ {
		if dp[n][j] < minTotal {
			minTotal = dp[n][j]
			remainingCoupons = j
		}
	}

	// 回溯找到使用优惠券的日期
	couponDays := []int{}
	currentJ := remainingCoupons
	currentCost := minTotal

	for i := n; i >= 1; i-- {
		price := p[i-1]
		// 检查是否是使用优惠券的情况
		if currentJ > 0 && dp[i-1][currentJ-1] == currentCost {
			couponDays = append(couponDays, i)
			currentJ--
			continue
		}

		// 检查是否是付费的情况
		prevJ := currentJ
		if price > 100 {
			prevJ = currentJ - 1
			if prevJ < 0 {
				prevJ = 0
			}
		}
		if prevJ >= 0 && dp[i-1][prevJ] != math.MaxInt32 && dp[i-1][prevJ]+price == currentCost {
			currentCost -= price
			currentJ = prevJ
			continue
		}
	}

	// 排序日期
	sort.Ints(couponDays)

	// 输出结果
	fmt.Println(minTotal)
	fmt.Printf("%d %d\n", remainingCoupons, len(couponDays))
	for _, day := range couponDays {
		fmt.Println(day)
	}
}
代码说明
  1. DP状态定义:dp[i][j]表示处理完前i天,剩余j张优惠券时的最小总成本,初始状态dp[0][0] = 0,其余状态初始化为math.MaxInt32表示不可达。
  2. 状态转移:
    • 付费分支:若前i-1天剩余j张优惠券,付费当前午餐后,总成本增加当前价格;若当前价格超过100,优惠券数量加1。
    • 使用优惠券分支:若前i-1天剩余j-1张优惠券,使用一张优惠券抵消当前午餐,总成本不变,剩余优惠券数量变为j。
  3. 回溯逻辑:从最后一天倒推,判断当天是使用优惠券还是付费,记录使用优惠券的日期。
  4. 边界处理:正确处理优惠券数量的上下限,避免越界;使用math.MaxInt32标记不可达状态,避免错误的最小值计算。

内容的提问来源于stack exchange,提问作者Павел Вайсберг

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 16:45:19