[Leaf] [ZJ2020E] Easy DP Problem

zj2020e---easy-dp-problem">ZJ2020E - Easy DP Problem

1. 题面梗概

中译中: 给定一个长度为 nn 的序列 aa。定义一个 DP 过程:

dp[i][j]={0(i=0)i2+dp[i−1][j](i>0,j=0)i2+max⁡(dp[i−1][j],dp[i−1][j−1]+b[i])(i>0,j>0)dp[i][j] = \begin{cases} 0 & (i = 0) \\ i^2 + dp[i - 1][j] & (i > 0, j = 0) \\ i^2 + \max(dp[i - 1][j], dp[i - 1][j - 1] + b[i]) & (i > 0, j > 0) \end{cases}

有 qq 次询问,每次询问截取 aa 的一段区间 [l,r][l, r] 作为序列 bb,求在这个 bb 下计算出的 dp[m][k]dp[m][k] 值(其中 m=r−l+1m = r - l + 1)。

2. 逻辑推导

问 dp[m][k]dp[m][k] 的值是多少。

我们先观察这个转移方程:dp[i][j]=i2+max⁡(dp[i−1][j],dp[i−1][j−1]+b[i])dp[i][j] = i^2 + \max(dp[i-1][j], dp[i-1][j-1] + b[i])。

令 dp[m][k]=∑i=1m(i2)+f(m,k),f(m,k)=max⁡(f(m−1,k),f(m−1,k−1)+b[i])dp[m][k] = \sum_{i=1}^{m}(i^2) + f(m,k), \\ f(m,k) = \max(f(m-1,k), f(m-1,k-1)+b[i])

只要我们把 i2i^2 拆出来的话,那么问题就转化为了:在这个常数项的基础上,如何从长度为 mm 的序列中选出 kk 个数,使得它们的和最大?

这不就是从区间 [l,r][l, r] 中选出最大的 kk 个数吗 —— 那么方案就很明确了,主席树。

我这里采用主席树来维护区间数值分布。在查询时,利用前缀根的差值定位到 [l,r][l, r] 对应的数值树,然后优先递归右子树(大值区)即可在 O(log⁡V)O(\log V) 内凑出前 kk 大的和。

3. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using PII = pair<int, int>;

const int maxn = 1e5 + 5;
const int maxV = 1e6 + 5;

int n, q, idx;
int root[maxn];

struct Node {
int l, r, cnt;
ll sum;
} tr[maxn << 5];

void modify( int &u, int v, int l, int r, int val )
{
u = ++ idx;
tr[u] = tr[v];
tr[u].cnt ++, tr[u].sum += val;
if( l == r ) return ;

int mid = ( l + r ) >> 1;
if( val <= mid ) modify( tr[u].l, tr[v].l, l, mid, val );
else modify( tr[u].r, tr[v].r, mid + 1, r, val );
}

ll query( int u, int v, int l, int r, int k )
{
if( l == r ) return l * k;

int mid = ( l + r ) >> 1;
int rcnt = tr[tr[v].r].cnt - tr[tr[u].r].cnt;

if( k <= rcnt ) return query( tr[u].r, tr[v].r, mid + 1, r, k );
else {
ll rsum = tr[tr[v].r].sum - tr[tr[u].r].sum;
return query( tr[u].l, tr[v].l, l, mid, k - rcnt ) + rsum;
}
}

ll calc( ll m )
{
return m * ( m + 1 ) * ( 2 * m + 1 ) / 6;
}

void solve( )
{
cin >> n;

idx = 0, root[0] = 0;
tr[0] = { 0, 0, 0, 0 };

for( int i = 1; i <= n; ++ i )
{
int x;
cin >> x;
modify( root[i], root[i - 1], 1, maxV, x );
}

cin >> q;
for( int i = 1; i <= q; ++ i )
{
int l, r, k;
cin >> l >> r >> k;
int m = r - l + 1;
// 常数项平方和 + 区间前 k 大和
ll ans = calc( m ) + query( root[l - 1], root[r], 1, maxV, k );
cout << ans << '\n';
}
}

int main( )
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int _t = 1;
cin >> _t;
while( _t -- )
{
solve( );
}
return 0;
}

4. 复盘

  • 复杂度: O((N+Q)log⁡V)O( (N+Q) \log V )。

  • 碎碎念: 看到复杂的转移方程不要慌,尝试把与决策无关的项(如 i2i^2)剥离出来,往往能化简得到模型本质。

  • 避雷:主席树的空间开销较大(Nlog⁡VN \log V),maxn << 5 | maxn * 32 是常用写法。

  • 关联笔记: [[主席树体系]]


[Leaf] [ZJ2020E] Easy DP Problem
http://miqiu.space/posts/c1cb10d6/
作者
Miqiu
发布于
2026年1月20日
许可协议