AtCoder Beginner Contest 234 G - Divide a Sequence
题目描述#
把一个长度为 的数组 , 分为几个连续的子序列 ,有 种划分方式
先给出数组 求出所有划分方式的价值之和,并对 取模.
对于一种划分方式 的价值为
对于一个子序列 ,其中最大的元素为 ,最小的元素为
思路#
首先我们可以想到一个 的
定义 为前 个数字的所有划分方式的价值之和
那么可以得到转移方程
(不包含 的原因是 单个数字的价值为 )
通过倒序遍历可以维护出 因此复杂度为
这么写肯定是会 的
因此我们考虑如何去优化
我们可以把 和 的贡献单独去考虑 (这也是常用的一个套路)
首先分析 #
转移方程
可以转换为
我们可以用 代表当前的 的价值和
那么 和 是否存在什么联系呢? 答案是存在的
我们考虑的是最大值对答案的贡献
对于 来说,产生贡献的最大值一定是单调上升的一些数字,因为我们 进行转移的时候是根据最后一段子序列进行分类的
是由 转移过来的,我们后缀最大值的贡献一定是在一个连续的区间,并且是单调递增的
那么我们就可以利用一个单调栈,每次弹出栈顶的时候减去栈顶的所有贡献,然后在最后加上当前位置的贡献即可
最小值同理
CODE
/********************
Author: Nanfeng1997
Contest: AtCoder - AtCoder Beginner Contest 234
URL: https://atcoder.jp/contests/abc234/tasks/abc234_g
When: 2022-03-16 10:28:26
Memory: 1024MB
Time: 2000ms
********************/
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MOD = 998244353;
inline int mod(int x) {return x >= MOD ? x - MOD : x;}
inline int ksm(int a, int b) {
int ret = 1; a = mod(a);
for(; b; b >>= 1, a = 1LL * a * a % MOD) if(b & 1) ret = 1LL * ret * a % MOD;
return ret;
}
template<int MOD>
struct modint {
int x;
modint() {x = 0; }
modint(int y) {x = y;}
inline modint inv() const { return modint{ksm(x, MOD - 2)}; }
explicit inline operator int() { return x; }
friend inline modint operator + (const modint &a, const modint& b) { return modint(mod(a.x + b.x)); }
friend inline modint operator - (const modint &a, const modint& b) { return modint(mod(a.x - b.x + MOD)); }
friend inline modint operator * (const modint &a, const modint& b) { return modint(1ll * a.x * b.x % MOD); }
friend inline modint operator - (const modint &a) { return modint(mod(MOD - a.x)); }
friend inline modint& operator += (modint &a, const modint& b) { return a = a + b; }
friend inline modint& operator -= (modint &a, const modint& b) { return a = a - b; }
friend inline modint& operator *= (modint &a, const modint& b) { return a = a * b; }
inline int operator == (const modint &b) { return x == b.x; }
inline int operator != (const modint &b) { return x != b.x; }
inline int operator < (const modint &a) { return x < a.x; }
inline int operator <= (const modint &a) { return x <= a.x; }
inline int operator > (const modint &a) { return x > a.x; }
inline int operator >= (const modint &a) { return x >= a.x; }
};
typedef modint<MOD> mint;
inline mint ksm(mint a, int b) {
mint ret = 1;
for(; b; b >>= 1, a = a * a ) if(b & 1) ret = ret * a ;
return ret;
}
const int N = 3e5 + 10;
int n;
int a[N], s1[N], s2[N];
mint dp[N], tr[N];
void add(int a, mint k) {
while(a <= n) tr[a] += k, a += a & -a;
}
mint query(int x) {
mint ret = 0; if(x >= 0) ret += 1; //树状数组的边界是1, 因此我们手动加上0处的贡献
while(x > 0) ret += tr[x], x -= x & -x;
return ret;
}
mint ask(int l, int r) {return query(r) - query(l - 1); }
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
int t1 = 0, t2 = 0;
mint mx = 0, mi = 0;
//dp[0] = 1, 因为我们进行转移的时候是乘法,乘法的幺元是 1
//mx 是最大值的贡献
//mi 是最小值的贡献
for(int i = 1; i <= n; i ++ ) {
while(t1 && a[s1[t1]] <= a[i]) {
int t = s1[t1 --];
mint ret = ask(s1[t1], t - 1);
mx = mx - ret * a[t];
}
mx = mx + ask(s1[t1], i) * a[i];
s1[++ t1] = i;
while(t2 && a[s2[t2]] >= a[i]) {
int t = s2[t2 --];
mint ret = ask(s2[t2], t - 1);
mi = mi - ret * a[t];
}
mi = mi + ask(s2[t2], i) * a[i];
s2[++ t2] = i;
dp[i] = mx - mi;
add(i, dp[i]);
}
printf("%d", (int)dp[n]);
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(nullptr);
int T = 1; //cin >> T;
while(T --) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】