总得分按行拆开后各行独立:单行做区间 DP,dp[l][r] 表示还剩 [l,r] 未取的最高分,本次取数轮次为 m-r+l,权重 2^(m-r+l) 用左移表达。
OJ: roj
题目 ID: 3558
难度:提高
标签:动态规划区间DPpython
创建: 2026-10-02 07:37
更新: 2026-10-10 11:30
形式化题目
给定
样例(
输入: 输出:
2 3 82
1 2 3
3 4 2一种最优取法:第一行依次取
正解
思路
朴素做法与瓶颈。 一行的取法由
观察一:行间独立。 把总得分按行重新分组:
每轮每行恰好取一个元素,所以第
观察二:行内是区间 DP。 对一行,取数永远发生在两端,取走一些元素后剩余部分仍是
一段连续区间;且剩余区间
边界是只剩一个元素的情形:它必然在第
样例第一行
| 剩余区间 |
含义 | 本次轮次 |
取行首: |
取行尾: |
|
|---|---|---|---|---|---|
| 只剩 1 | 3 | — | — | ||
| 只剩 2 | 3 | — | — | ||
| 只剩 3 | 3 | — | — | ||
| 剩 1,2 | 2 | ||||
| 剩 2,3 | 2 | ||||
| 全部剩余 | 1 |
表中先填对角线(单元素边界),再按区间长度递增填
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 十进制大整数(little-endian),支持加法/乘法/比较
struct BigInt {
vector<int> d;
BigInt() { d.push_back(0); }
BigInt(ll x) { d.clear(); if (x == 0) d.push_back(0); else while (x) { d.push_back((int)(x % 10)); x /= 10; } }
void trim() { while (d.size() > 1 && d.back() == 0) d.pop_back(); }
bool is_zero() const { return d.size() == 1 && d[0] == 0; }
int cmp(const BigInt& o) const {
if (d.size() != o.d.size()) return d.size() < o.d.size() ? -1 : 1;
for (int i = (int)d.size() - 1; i >= 0; i--)
if (d[i] != o.d[i]) return d[i] < o.d[i] ? -1 : 1;
return 0;
}
bool operator<(const BigInt& o) const { return cmp(o) < 0; }
string str() const { string r; for (int i = (int)d.size() - 1; i >= 0; i--) r += char('0' + d[i]); return r; }
BigInt operator+(const BigInt& o) const {
BigInt r; r.d.assign(max(d.size(), o.d.size()) + 1, 0);
for (size_t i = 0; i < d.size(); i++) r.d[i] += d[i];
for (size_t i = 0; i < o.d.size(); i++) r.d[i] += o.d[i];
for (size_t i = 0; i + 1 < r.d.size(); i++) { r.d[i + 1] += r.d[i] / 10; r.d[i] %= 10; }
r.trim(); return r;
}
BigInt operator*(const BigInt& o) const {
BigInt r; r.d.assign(d.size() + o.d.size(), 0);
for (size_t i = 0; i < d.size(); i++)
for (size_t j = 0; j < o.d.size(); j++)
r.d[i + j] += d[i] * o.d[j];
for (size_t i = 0; i + 1 < r.d.size(); i++) { r.d[i + 1] += r.d[i] / 10; r.d[i] %= 10; }
r.trim(); return r;
}
};
int n, m;
ll row[85];
BigInt pow2[85]; // pow2[i] = 2^i
BigInt row_score() {
// dp[l][r]:还剩 [l,r] 未取时已拿到的最高分
static BigInt dp[85][85];
for (int l = m - 1; l >= 0; l--) {
dp[l][l] = pow2[m] * row[l];
for (int r = l + 1; r < m; r++) {
int shift = m - r + l;
BigInt take_left = pow2[shift] * row[l] + dp[l + 1][r];
BigInt take_right = pow2[shift] * row[r] + dp[l][r - 1];
dp[l][r] = take_left < take_right ? take_right : take_left;
}
}
return dp[0][m - 1];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
pow2[0] = BigInt(1);
for (int i = 1; i <= m; i++) pow2[i] = pow2[i - 1] * BigInt(2);
BigInt total(0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) cin >> row[j];
total = total + row_score();
}
cout << total.str() << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 07:37
# update_at: 2026-10-02 07:37
import sys
def row_score(row: list[int]) -> int:
"""一行取数的最大得分:dp[l][r] 表示还剩 [l,r] 未取时,这一行已拿到的最高分。
第 k 次取的权重是 2^k;区间长 L 时已取 m-L 个,本次是第 m-L+1 次取,
所以权重 = 2^(m-L+1) = row 元素左移 (m-r+l) 位。
"""
m = len(row)
dp = [[0] * m for _ in range(m)]
for l in range(m - 1, -1, -1):
dp[l][l] = row[l] << m # 只剩一个时它是第 m 次被取
for r in range(l + 1, m):
shift = m - r + l # 本次取数的轮次编号
take_left = (row[l] << shift) + dp[l + 1][r] # 取行首,剩 [l+1,r]
take_right = (row[r] << shift) + dp[l][r - 1] # 取行尾,剩 [l,r-1]
dp[l][r] = max(take_left, take_right)
return dp[0][m - 1]
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, m = next(data), next(data)
# 各行独立(每行权重序列相同),总分 = 各行最优得分之和
total = 0
for _ in range(n):
row = [next(data) for _ in range(m)]
total += row_score(row)
print(total)
if __name__ == "__main__":
solve()复杂度
- 时间:单行
个状态、每个状态 转移, 行合计 , 时约 次大整数运算,与 C++ 正解同阶。 - 空间:单行
的 dp表,各行复用后丢弃,合计。 - C++ 落地时答案上界约
,需 unsigned __int128或高精度; Python 的int任意精度,x << k即完成。
总结
本题两步拆解:先按"每轮得分对行求和是线性的"把矩阵游戏拆成 shift = m - r + l 一行同时表达轮次与权重。