带权选数

  • 难度 普及−
  • 时限 1s
  • 空限 256m
  • 线性DP

数据规模:1≤M≤N≤20001 \le M \le N \le 2000,−109≤ai≤109-10^9 \le a_i \le 10^9。

思路

按原相对顺序恰好选 MM 个数,第 jj 个被选中的数贡献 j×aij \times a_i,是经典的线性 DP。

设 dp[j]dp[j] 表示处理完当前前缀、共选了 jj 个数时的最大总分。扫描到 aia_i 时有两种决策:

  • 不选 aia_i:dp[j]dp[j] 保持不变;
  • 选 aia_i:它成为第 jj 个被选中的数,由 dp[j−1]dp[j-1] 转移,得到 dp[j−1]+j×aidp[j-1] + j \times a_i。

转移方程为 dp[j]=max⁡(dp[j], dp[j−1]+j×ai)dp[j] = \max(dp[j],\ dp[j-1] + j \times a_i)。内层让 jj 从 MM 倒序枚举到 11,这样 dp[j−1]dp[j-1] 仍是上一轮的值,一维数组即可完成滚动更新,无需记录位置维度。

由于 aia_i 可以为负,初值把 dp[1..M]dp[1..M] 全部置为 −∞-\infty(取 −4×1018-4 \times 10^{18},加上转移量 j×aij \times a_i 也不会溢出),只有 dp[0]=0dp[0] = 0。因为 M≤NM \le N,合法方案必然存在,最终答案即 dp[M]dp[M]。

复杂度

时间 O(N×M)O(N \times M),额外空间 O(M)O(M)。答案最大约为 ∑j=1Mj×109≈2×1015\sum_{j=1}^{M} j \times 10^9 \approx 2 \times 10^{15},Int64 足够容纳。

仓颉实现

查看仓颉实现
import std.env.*
import std.convert.*

main() {
    let reader = getStdIn()
    let nm = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let n = nm[0]
    let m = nm[1]
    let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let NEG: Int64 = -4000000000000000000
    let dp = Array<Int64>(m + 1, { _ => NEG })
    dp[0] = 0
    var i: Int64 = 0
    while (i < n) {
        var j = m
        while (j >= 1) {
            let cand = dp[j - 1] + j * a[i]
            if (cand > dp[j]) {
                dp[j] = cand
            }
            j -= 1
        }
        i += 1
    }
    println(dp[m])
}