题目笔记——P12547 [UOI 2025] Simple Subsequence
建议先做上面两道题目,此题就是在上面题的基础上添加了修改操作。
题目大意
给定一个长度为 \(n\) 的数组和 \(q\) 次查询,每次查询有两种类型,第一种是将一个点修改成它的相反数,第二种是给定一个区间 \((l,r)\),求出区间内最长的好的子序列的长度,满足子序列中所有的前后缀大于等于 \(0\)。
解题思路
转化
在一个区间内,我们可以发现子段和为区间和减该子段左边区间内前缀和和该子段右边区间内后缀和之和的差,所以:区间和减子段和等于前缀和加后缀和。
如果有子段和大于区间和的情况,就需要通过删除元素来维持。
那么我们可以把这道题的最终答案最长的好的子序列的长度就可以转化成区间长度减最少删除次数。
删除操作
接下来我们思考:我们应该删除什么元素?怎么删?
- 首先我们不删 \(1\),只删 \(-1\) 保证是最优的。
- 我们在子段和大于区间和的情况下想尽量通过删除操作让子段和变小或让区间和变大,通过删除操作让子段和变小的操作只有在子段和中删除 \(1\),但同时外面的区间和也会随之变小。所以只有通过删除操作让区间和变大可行。
那如何保证通过删除操作让区间和变大的同时子段和不变或变小?我们可以每次都删除整个区间内但在子段和外的 \(-1\) 来使区间和变大,而子段和不变。
可以证明:绝对可以在删除完所有在整个区间内但在子段和外的 \(-1\) 或之前达到区间和大于等于子段和,因为当子段外的 \(-1\) 全部删完后,区间和就是子段和加子段外 \(1\) 的个数。
线段树维护
根据上面的分析,因为要保证最终区间和大于等于子段和,所以只要保证最大子段和小于等于区间和即可,所以最少删除次数就是最大子段和减区间和,如果区间和本身就大于最大子段和,那么最少删除次数就为 \(0\)。
那么我们用线段树维护区间最大子段和即可。
数组要开大(反正我的代码只有开够了才能过)。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define left (now << 1)
#define right ((now << 1) | 1)
#define mid ((l + r) >> 1)
const int N = 5e5 + 20, M = 2e6 + 20;
struct node{
int l, r, mx, mn, up, sum;
}e[M];
int n, q, a[N], pre[N], tag[M];
void push_up(node &now, node lt, node rt){
now.mn = min(lt.mn, rt.mn);
now.mx = max(lt.mx, rt.mx);
now.up = max({rt.mx - lt.mn, lt.up, rt.up});
now.sum = lt.sum + rt.sum;
return ;
}
void push_down(int now){
e[left].mn += tag[now];
e[right].mn += tag[now];
e[left].mx += tag[now];
e[right].mx += tag[now];
e[left].sum += tag[now] * (e[left].r - e[left].l + 1);
e[right].sum += tag[now] * (e[right].r - e[right].l + 1);
tag[left] += tag[now];
tag[right] += tag[now];
tag[now] = 0;
return ;
}
void build(int now, int l, int r){
e[now].l = l, e[now].r = r;
if(l == r){
e[now].mx = e[now].mn = e[now].sum = pre[l];
e[now].up = 0;
return ;
}
build(left, l, mid);
build(right, mid + 1, r);
push_up(e[now], e[left], e[right]);
return ;
}
void update(int now, int l, int r, int k){
if(l <= e[now].l && e[now].r <= r){
e[now].mn += k;
e[now].mx += k;
e[now].sum += k * (e[now].r - e[now].l + 1);
tag[now] += k;
return ;
}
push_down(now);
if(e[left].r >= l) update(left, l, r, k);
if(e[right].l <= r) update(right, l, r, k);
push_up(e[now], e[left], e[right]);
return ;
}
node query(int now, int l, int r){
if(l <= e[now].l && e[now].r <= r) return e[now];
node ans;
ans.mn = 1e18, ans.mx = -1e18, ans.up = 0, ans.sum = 0;
push_down(now);
if(e[left].r >= l) push_up(ans, ans, query(left, l, r));
if(e[right].l <= r) push_up(ans, ans, query(right, l, r));
return ans;
}
int op, l, r, ans;
signed main(){
scanf("%lld%lld", &n, &q);
for(int i = 1; i <= n; i++){
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
build(1, 0, n);
while(q --){
scanf("%lld%lld", &op, &l);
if(op == 1){
update(1, l, n, (-2 * a[l]));
a[l] = -a[l];
}
else{
scanf("%lld", &r);
ans = r - l + 1 - (query(1, l - 1, r).up - (query(1, r, r).sum - query(1, l - 1, l - 1).sum));
printf("%lld\n", ans);
}
}
return 0;
}

浙公网安备 33010602011771号