题目描述

霍格沃茨之战即将开始。食死徒们发明了一种使用魔杖增强力量的新方法。每个食死徒可以携带两根魔杖(左手一根,右手一根),他们站成一排组成军队的前线。每个食死徒有一个强度值,初始力量值111

每个食死徒可以通过以下方式增加自己的力量值:

  • 左手魔杖连接左侧的另一个食死徒,要求对方的强度严格小于自己的强度,并且位置在自己左边
  • 右手魔杖连接右侧的另一个食死徒,要求对方的强度严格小于自己的强度,并且位置在自己右边

通过这种方式,食死徒可以构建一个序列:强度从左到右递增,在自己位置达到最大,然后再递减。该食死徒的力量值等于这个序列的长度,且每个食死徒都会最大化自己的力量值。

在确定所有食死徒的力量值后,赫敏需要施展分裂咒语,将整条线(或某一段)分成两部分。每次分裂的成本等于该段内所有食死徒的力量值之和。目标是通过若干次分裂,使每个食死徒都单独成段,并最小化总成本

输入格式

  • 第一行一个正整数 TTTT≤300T \leq 300T300),表示测试用例数
  • 每个测试用例:
    • 第一行一个整数 nnn1≤n≤10001 \leq n \leq 10001n1000),表示食死徒人数
    • 第二行 nnn 个正整数,表示从左到右的食死徒强度值(所有值小于 10610^6106

输出格式

对每个测试用例输出 Case <x>: <y>,其中 xxx 为用例编号(从 111 开始),yyy 为最小总成本。

题目分析

第一步:计算每个位置的力量值

对于位置 iii,设:

  • L[i]L[i]L[i] 表示从位置 iii 向左看,强度严格递增的最长序列长度(包括 iii 自身)
  • R[i]R[i]R[i] 表示从位置 iii 向右看,强度严格递减的最长序列长度(包括 iii 自身)

那么位置 iii 的力量值为:
power[i]=L[i]+R[i]−1 power[i] = L[i] + R[i] - 1 power[i]=L[i]+R[i]1

因为 iii 在左边递增序列和右边递减序列中被重复计算了一次。

L\texttt{L}L 数组的计算方法

对于每个 iii,向左扫描所有 j<ij < ij<i,如果 a[j]<a[i]a[j] < a[i]a[j]<a[i](强度严格小于自己),则:
L[i]=max⁡(L[i],L[j]+1) L[i] = \max(L[i], L[j] + 1) L[i]=max(L[i],L[j]+1)
初始值 L[i]=1L[i] = 1L[i]=1(只有自己)。

R\texttt{R}R 数组的计算方法

对于每个 iii,向右扫描所有 j>ij > ij>i,如果 a[j]<a[i]a[j] < a[i]a[j]<a[i](强度严格小于自己),则:
R[i]=max⁡(R[i],R[j]+1) R[i] = \max(R[i], R[j] + 1) R[i]=max(R[i],R[j]+1)
初始值 R[i]=1R[i] = 1R[i]=1

时间复杂度 O(n2)O(n^2)O(n2),对于 n≤1000n \leq 1000n1000 可以接受。

第二步:转化为区间分割问题

已知每个位置的力量值 power[i]power[i]power[i],我们需要将区间 [1,n][1, n][1,n] 通过不断分裂,最终得到 nnn 个长度为 111 的区间。每次分裂一段区间 [l,r][l, r][l,r] 的成本等于 ∑k=lrpower[k]\sum_{k=l}^{r} power[k]k=lrpower[k]

这是一个经典的最优二叉搜索树矩阵链乘类型的区间 DP\texttt{DP}DP 问题。

定义状态

dp[l][r]dp[l][r]dp[l][r] 表示将区间 [l,r][l, r][l,r] 分裂成单个元素所需的最小总成本。

边界条件

当区间长度为 111 时,不需要分裂:
dp[l][l]=0 dp[l][l] = 0 dp[l][l]=0

状态转移

对于区间 [l,r][l, r][l,r](长度 ≥2\geq 22),选择一个分割点 kkkl≤k<rl \leq k < rlk<r),进行如下操作:

  1. 将区间 [l,r][l, r][l,r] 分裂成 [l,k][l, k][l,k][k+1,r][k+1, r][k+1,r]
  2. 本次分裂的成本为 ∑t=lrpower[t]\displaystyle \sum_{t=l}^{r} power[t]t=lrpower[t],记作 sum(l,r)sum(l, r)sum(l,r)
  3. 之后需要继续分裂两个子区间,成本为 dp[l][k]+dp[k+1][r]dp[l][k] + dp[k+1][r]dp[l][k]+dp[k+1][r]

因此:
dp[l][r]=min⁡k=lr−1{dp[l][k]+dp[k+1][r]}+sum(l,r) dp[l][r] = \min_{k=l}^{r-1} \{ dp[l][k] + dp[k+1][r] \} + sum(l, r) dp[l][r]=k=lminr1{dp[l][k]+dp[k+1][r]}+sum(l,r)

朴素做法的时间复杂度

枚举区间长度 O(n)O(n)O(n),枚举左端点 O(n)O(n)O(n),枚举分割点 O(n)O(n)O(n),总复杂度 O(n3)O(n^3)O(n3)。对于 n≤1000n \leq 1000n1000n3=109n^3 = 10^9n3=109,再乘上 T≤300T \leq 300T300,会严重超时。

第三步:使用 Knuth\texttt{Knuth}Knuth 优化

观察转移方程:
dp[l][r]=min⁡k=lr−1{dp[l][k]+dp[k+1][r]}+sum(l,r) dp[l][r] = \min_{k=l}^{r-1} \{ dp[l][k] + dp[k+1][r] \} + sum(l, r) dp[l][r]=k=lminr1{dp[l][k]+dp[k+1][r]}+sum(l,r)

其中 sum(l,r)sum(l, r)sum(l,r)kkk 无关,可以提取出来:
dp[l][r]=min⁡k=lr−1{dp[l][k]+dp[k+1][r]}+sum(l,r) dp[l][r] = \min_{k=l}^{r-1} \{ dp[l][k] + dp[k+1][r] \} + sum(l, r) dp[l][r]=k=lminr1{dp[l][k]+dp[k+1][r]}+sum(l,r)

这个形式满足四边形不等式,因此可以使用 Knuth\texttt{Knuth}Knuth 优化。

Knuth\texttt{Knuth}Knuth 优化的核心思想

K[l][r]K[l][r]K[l][r] 表示使 dp[l][r]dp[l][r]dp[l][r] 取得最小值的最优分割点 kkk。对于满足四边形不等式的 DP\texttt{DP}DP,最优分割点具有单调性
K[l][r−1]≤K[l][r]≤K[l+1][r] K[l][r-1] \leq K[l][r] \leq K[l+1][r] K[l][r1]K[l][r]K[l+1][r]

这意味着当我们计算 dp[l][r]dp[l][r]dp[l][r] 时,不需要枚举所有 kkk,只需要枚举:
k∈[K[l][r−1],  K[l+1][r]] k \in [K[l][r-1],\; K[l+1][r]] k[K[l][r1],K[l+1][r]]

由于每个区间 [l,r][l, r][l,r] 的枚举范围均摊为 O(1)O(1)O(1),总时间复杂度降为 O(n2)O(n^2)O(n2)

初始化 K\texttt{K}K 数组

对于长度为 111 的区间,设置 K[i][i]=iK[i][i] = iK[i][i]=i(虽然不会使用,但为了方便边界条件)。

计算顺序

按照区间长度从小到大的顺序计算,保证计算 dp[l][r]dp[l][r]dp[l][r] 时,dp[l][r−1]dp[l][r-1]dp[l][r1]dp[l+1][r]dp[l+1][r]dp[l+1][r] 已经计算完毕。

第四步:前缀和优化

为了快速计算 sum(l,r)=∑t=lrpower[t]sum(l, r) = \sum_{t=l}^{r} power[t]sum(l,r)=t=lrpower[t],我们预处理前缀和数组 preprepre
pre[i]=∑t=0i−1power[t] pre[i] = \sum_{t=0}^{i-1} power[t] pre[i]=t=0i1power[t]
则有:
sum(l,r)=pre[r+1]−pre[l] sum(l, r) = pre[r+1] - pre[l] sum(l,r)=pre[r+1]pre[l]

算法步骤总结

  1. 读入 TTT,对每个测试用例:
  2. 读入 nnn 和强度数组 aaa
  3. 计算 LLL 数组:对于每个 iii,向左扫描找严格递增序列的最大长度
  4. 计算 RRR 数组:对于每个 iii,向右扫描找严格递减序列的最大长度
  5. 计算 power[i]=L[i]+R[i]−1power[i] = L[i] + R[i] - 1power[i]=L[i]+R[i]1
  6. 计算 powerpowerpower 的前缀和数组 preprepre
  7. 初始化 dpdpdp 数组为 000K[i][i]=iK[i][i] = iK[i][i]=i
  8. 按区间长度从小到大进行 DP\texttt{DP}DP,利用 Knuth\texttt{Knuth}Knuth 优化缩小 kkk 的枚举范围
  9. 输出 dp[0][n−1]dp[0][n-1]dp[0][n1]

复杂度分析

  • 计算 LLLRRRO(n2)O(n^2)O(n2)
  • Knuth\texttt{Knuth}Knuth 优化区间 DP\texttt{DP}DPO(n2)O(n^2)O(n2)
  • 总复杂度:O(n2)O(n^2)O(n2),对于 n≤1000n \leq 1000n1000 可以轻松通过 T≤300T \leq 300T300 的测试数据

代码实现

// Gain Battle Power
// UVa ID: 12836
// Verdict: Accepted
// Submission Date: 2026-04-26
// UVa Run Time: 2.210s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int INF = 1e9;

int main() {
    int T;
    scanf("%d", &T);
    for (int caseNo = 1; caseNo <= T; ++caseNo) {
        int n;
        scanf("%d", &n);
        vector<int> a(n);
        for (int i = 0; i < n; ++i) scanf("%d", &a[i]);

        // 计算 L[i]:向左严格递增的最长长度
        vector<int> L(n, 1), R(n, 1);
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < i; ++j)
                if (a[j] < a[i]) L[i] = max(L[i], L[j] + 1);

        // 计算 R[i]:向右严格递减的最长长度
        for (int i = n - 1; i >= 0; --i)
            for (int j = i + 1; j < n; ++j)
                if (a[j] < a[i]) R[i] = max(R[i], R[j] + 1);

        // 力量值
        vector<int> power(n);
        for (int i = 0; i < n; ++i) power[i] = L[i] + R[i] - 1;

        // 前缀和
        vector<int> pre(n + 1, 0);
        for (int i = 0; i < n; ++i) pre[i + 1] = pre[i] + power[i];

        // 区间DP + Knuth优化
        vector<vector<int>> dp(n, vector<int>(n, 0));
        vector<vector<int>> K(n, vector<int>(n, 0));

        // 初始化:长度为1的区间不需要分割点
        for (int i = 0; i < n; ++i) K[i][i] = i;

        // 枚举区间长度
        for (int len = 2; len <= n; ++len) {
            for (int l = 0; l + len <= n; ++l) {
                int r = l + len - 1;
                int sumLR = pre[r + 1] - pre[l];
                dp[l][r] = INF;

                // 利用 Knuth 优化缩小 k 的范围
                int kMin = K[l][r - 1];
                int kMax = K[l + 1][r];
                for (int k = kMin; k <= kMax && k < r; ++k) {
                    int val = dp[l][k] + dp[k + 1][r] + sumLR;
                    if (val < dp[l][r]) {
                        dp[l][r] = val;
                        K[l][r] = k;
                    }
                }
            }
        }

        printf("Case %d: %d\n", caseNo, dp[0][n - 1]);
    }
    return 0;
}

总结

本题的核心难点有两个:

  1. 正确理解力量值的计算方式:每个食死徒的力量值等于其左侧严格递增序列长度加上右侧严格递减序列长度减 111。这是一个典型的 LIS\texttt{LIS}LIS(最长递增子序列)变种问题。

  2. 将分裂问题转化为区间 DP\texttt{DP}DP:每次分裂的成本是区间内所有力量值之和,这类似于矩阵链乘问题,但成本函数与分割点无关,因此可以使用 Knuth\texttt{Knuth}Knuth 优化将 O(n3)O(n^3)O(n3) 降为 O(n2)O(n^2)O(n2)

通过这两个步骤的合理结合,即可在给定数据范围内高效求解。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐