AtCoder Beginner Contest 234 G - Divide a Sequence
题目
给定一个长度为N的序列,任意的将其划分为任意长度大于零的若干个子串,对于任意一个子串 S i S_i Si有一个权值 m a x ( S i ) − m i n ( S i ) max(S_i) - min(S_i) max(Si)−min(Si),将其设为 V i V_i Vi,对于整个序列,其权值 W = V 1 ∗ V 2 ∗ V 3 … ∗ V k W = V_1 * V_2 * V_3 \ldots * V_k W=V1∗V2∗V3…∗Vk,现求对于长度为N的序列,所有可能划分产生的 W W W的和 S u m Sum Sum
思路
我们用DP的思想思考这个问题,如果我们知道了长
1
1
1到
i
−
1
i - 1
i−1的序列的
S
u
m
Sum
Sum,那么对于长度为
i
i
i的序列,其和前状态本质在于所有加入
a
i
a_i
ai,的子串,对于这样一个子串,我们设其为{
a
j
,
a
j
+
1
,
a
j
+
2
,
…
,
a
i
a_j,a_{j+1},a_{j+2},\ldots,a_i
aj,aj+1,aj+2,…,ai},我们可以算出它的
v
v
v,可得
d
p
[
i
]
=
d
p
[
i
]
+
d
p
[
i
−
j
−
1
]
∗
v
dp[i] = dp[i] + dp[i - j - 1] * v
dp[i]=dp[i]+dp[i−j−1]∗v
枚举j,可以得到朴素的转移方程
d
p
i
=
∑
j
=
0
i
d
p
j
∗
max
(
a
j
+
1
,
a
j
+
2
,
…
,
a
i
)
−
d
p
j
∗
min
(
a
j
+
1
,
a
j
+
2
,
…
,
a
i
)
dp_i = \sum_{j = 0}^i {dp_j*\max(a_{j + 1},a_{j + 2},\ldots,a_i) - dp_j*\min(a_{j + 1},a_{j + 2},\ldots,a_i) }
dpi=j=0∑idpj∗max(aj+1,aj+2,…,ai)−dpj∗min(aj+1,aj+2,…,ai)
如果直接暴力枚举的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2)的,无法通过本题,考虑优化
首先,
m
a
x
max
max和
m
i
n
min
min可以分开计算
d
p
i
=
∑
j
=
0
i
d
p
j
∗
max
(
a
j
+
1
,
a
j
+
2
,
…
,
a
i
)
−
∑
j
=
0
i
d
p
j
∗
min
(
a
j
+
1
,
a
j
+
2
,
…
,
a
i
)
dp_i = \sum_{j = 0}^i {dp_j *\max(a_{j + 1},a_{j + 2},\ldots,a_i) } -\sum_{j = 0}^i {dp_j *\min(a_{j + 1},a_{j + 2},\ldots,a_i)}
dpi=j=0∑idpj∗max(aj+1,aj+2,…,ai)−j=0∑idpj∗min(aj+1,aj+2,…,ai)
对于一段后缀最值相关可以尝试用单调栈优化
观察以下dp转移方程
dp[1] = dp[0] * (max(a[1]) - min(a[1]))
dp[2] = dp[0] * (max(a[1],a[2]) - min(a[1],a[2]))
+ dp[1] * (max(a[2]) - max(a[1]))
dp[3] = dp[0] * (max(a[1],a[2],a[3]) - min(a[1],a[2],a[3]))
+ dp[1] * (max(a[2],a[3]) - min(a[2],a[3]))
+ dp[2] * (max(a[3]) - min(a[3]))
可以发现,答案都是一段后缀最值乘以某个dp值,最后对这些值求前缀和的过程,但每一次加入新的元素,这个前缀和的某些项就会发生改变。
这个过程可以说是用单调栈维护区间最值的同时,动态维护一段前缀和
m
x
s
u
m
mxsum
mxsum的过程。(对于最小值同理)
m
x
s
u
m
=
∑
j
=
0
i
d
p
j
∗
max
(
a
j
+
1
,
a
j
+
2
,
…
,
a
i
)
mxsum = \sum_{j = 0}^i {dp_j *\max(a_{j + 1},a_{j + 2},\ldots,a_i) }
mxsum=j=0∑idpj∗max(aj+1,aj+2,…,ai)
考虑如何用单调栈维护
m
x
s
u
m
mxsum
mxsum,我们设
m
x
s
u
m
mxsum
mxsum的任意项为
s
i
s_i
si
对于单调栈,它维护的是第i个元素作为后缀最值的最长后缀
[
l
,
i
]
[l,i]
[l,i],那么
[
l
,
i
]
[l,i]
[l,i]的所有后缀子段都会把最值修改为
a
i
a_i
ai,也即我们要更新的
s
i
s_i
si。(或者说是由
a
i
a_i
ai,所统领的所有
s
s
s)
因此每次弹出栈内元素的过程中,如果我们知道弹出元素所统领的 s i s_i si中的 d p [ ] dp[] dp[]值都是哪些,就可以完成对 m x s u m mxsum mxsum的一次更新。
统计这些
d
p
[
]
dp[]
dp[]值,同样可以通过定义一个这样的
d
p
dp
dp数组:
d
p
m
x
i
dpmx_i
dpmxi表示由第i个元素所统领的
s
s
s所属的
d
p
[
]
dp[]
dp[]的和。
可写出转移方程:
d
p
m
x
i
=
d
p
m
x
i
+
d
p
m
x
j
dpmx_i = dpmx_i + dpmx_j
dpmxi=dpmxi+dpmxj
而j就是单调栈弹出的元素的下标。
这样我们就可以在单调栈维护过程中顺势维护了
m
x
s
u
m
mxsum
mxsum。对于
m
n
s
u
m
mnsum
mnsum同理。
最后,成功的把时间复杂度优化到了 O ( n ) O(n) O(n)。
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define linf 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define debug(x) cout<<"> "<< x<<endl;
#define endl '\n'
#define lowbit(x) x&-x
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int MAXN =10 + 2e5, mod = 998244353;
void solve()
{
int N;
cin >> N;
vector<int> a(N + 1);
for(int i = 1;i <= N;i ++) cin >> a[i];
// dp[i] = sum(dp[j] * max(a[j + 1 ~ i])) - sum(dp[j] * min(a[j + 1 ~ i]))
vector<ll> dp(N + 1,0),dpmx(N + 1,0),dpmn(N + 1,0);
dp[0] = dpmx[0] = dpmn[0] = 1;
stack<int> Mx,Mn;
int mxsum = 0,mnsum = 0;
for(int i = 1;i <= N;i ++){
dpmx[i] = dp[i - 1];// 长为1的后缀一定由ai统领
while(Mx.size() && a[Mx.top()] < a[i]) {
int id = Mx.top();
Mx.pop();
dpmx[i] = (dpmx[i] + dpmx[id]) % mod;// 求须修改项的dp[]和
mxsum = (mxsum - dpmx[id] * a[id] % mod + mod) % mod;// 先减掉须修改值
}
Mx.push(i);
mxsum = (mxsum + dpmx[i] * a[i] % mod + mod) % mod;// 加上修改后的值
// 完成一次对mxsum的维护
dpmn[i] = dp[i - 1];
while(Mn.size() && a[Mn.top()] > a[i]) {
int id = Mn.top();
Mn.pop();
dpmn[i] = (dpmn[i] + dpmn[id]) % mod;
mnsum = (mnsum - dpmn[id] * a[id] % mod + mod) % mod;
}
Mn.push(i);
mnsum = (mnsum + dpmn[i] * a[i] % mod + mod) % mod;
dp[i] = (mxsum - mnsum + mod) % mod;
}
cout << dp[N] << endl;
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
solve();
return 0;
}
后记
算是我的单调栈和单调队列入门题了···,从知道这个概念到AC此题对我而言真的是个很困难的过程。中间查了资料,写了模板题,才回来理清这题的思路。十分曲折了可以说。所以尽力写好这篇博客。可最后还是有些不尽人意,感觉还有很多地方说的啰嗦或者不够切中要害,还是对单调栈和DP的理解不够深入。