题目笔记——P4786 [BalkanOI 2018] Election
类似的题目(双倍经验)。
题目大意
给定一个长度为 \(n\) 的字符串和 \(q\) 次询问,每次询问给定一个区间 \((l,r)\),每次可以在原序列上的 \((l,r)\) 区间内删除一些数,剩余未被删除的数将按照原序列顺序排列,使得最终的这个子序列满足其所有非空前后缀 \(C\) 的个数不少于 \(T\) 的个数,求子序列的最长长度。
解题思路
转化
在一个区间内,我们可以发现子段和为区间和减该子段左边区间内前缀和和该子段右边区间内后缀和之和的差,所以区间和减子段和等于前缀和加后缀和。
那既然我们要使区间内所有非空前后缀里 \(C\) 的个数不少于 \(T\) 的个数,可以先令 \(T\) 为 \(-1\),\(C\) 为 \(1\),求出整个序列的前缀和和后缀和,这样题目就转化成了删除最少个数,使得剩余子序列的所有前后缀大于等于 \(0\)。
根据刚才得出的式子,我们发现要保证所有先后缀大于等于 \(0\),那么区间和减子段和就必须大于等于 \(0\)。
如果有子段和大于区间和的情况,就需要通过删除元素来维持。
删除操作
接下来我们思考:我们应该删除什么元素?怎么删?
- 既然要保证删除次数最小,那么就要去除不必要的删除,要保证最后 \(0\) 的个数不多于 \(1\) 的个数,所以我们不删 \(1\),只删 \(0\) 保证是最优的。
- 我们在子段和大于区间和的情况下想尽量通过删除操作让子段和变小或让区间和变大,通过删除操作让子段和变小的操作只有在子段和中删除 \(1\),但同时外面的区间和也会随之变小。所以只有通过删除操作让区间和变大可行。
那如何保证通过删除操作让区间和变大的同时子段和不变或变小?我们可以每次都删除整个区间内但在子段和外的 \(T\),因为刚刚求的前后缀中令 \(T\) 为 \(-1\),所以可以通过删除 \(T\) 来使区间和变大,而子段和不变。
可以证明:绝对可以在删除完所有在整个区间内但在子段和外的 \(T\) 或之前达到区间和大于等于子段和,因为当子段外的 \(T\) 全部删完后,区间和就是子段和加子段外 \(C\) 的个数。
线段树维护
根据上面的分析,因为要保证最终区间和大于等于子段和,所以只要保证最大子段和小于等于区间和即可,所以最少删除次数就是最大子段和减区间和,如果区间和本身就大于最大子段和,那么最少删除次数就为 \(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;
}e[M];
int n, q, pre[N], tag[N];
string s;
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});
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 = 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 ;
}
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;
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 l, r;
node res;
signed main(){
cin >> n >> s >> q;
s = ' ' + s;
for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] + ((s[i] == 'C') ? 1 : -1);
build(1, 0, n);
while(q --){
scanf("%lld%lld", &l, &r);
res = query(1, l - 1, r);
printf("%lld\n", res.up - (pre[r] - pre[l - 1]));
}
return 0;
}

浙公网安备 33010602011771号