题意#
有一个长度为 的数字序列 ,对这个序列的任意一个连续子串,求所有数字之和,重复出现的数字只被统计一次。问第 大的和是多少。
,,.
思路#
下面提到的所有区间和均为去重后的区间和。
考虑用线段树维护。
以右端点为横坐标,第 棵线段树维护:以 作为左端点的所有区间和的最大值。
先考虑第 棵线段树。由于 较大,先借助 处理出去重后的前缀和。
for(int i = 1; i <= n; i++) {
pre_sum[i] = pre_sum[i - 1];
if(!mp[in[i]]) {
pre_sum[i] += in[i];
mp[in[i]] = 1;
}
}
cpp这样我们就知道了:所有以 作为左端点的区间和。依此直接建树即可得到第 棵线段树。
对于后面的 棵线段树,如果采取同样的方法,时间和空间都会炸。
我们发现,第 棵线段树和第 棵线段树差距不大,因此使用可持久化线段树,使线段树之间共享节点。
进一步,从第 棵线段树转移到第 棵线段树,只变了 到下一次 出现的位置之前这段区间。右端点在这段区间内的线段都减少了.
同样,我们借助 预处理出每个元素 下一次出现的位置 ,每次就可以通过上一棵线段树直接转移出当前所求。
void add(int &id, int his, int l, int r, int L, int R, int v) {}
for(int l = 2; l <= n; l++) { //l为左端点
root[l] = root[l - 1];
add(root[l], root[l - 1], 1, n, l - 1, nxt[l - 1] - 1, -in[l - 1]);
add(root[l], root[l], 1, n, l - 1, l - 1, -inf);
}
cpp用主席树维护之后,开始解决第 大的问题。
由于 不大, 的复杂度可以接受,直接用大根堆维护。
先找出以每个 作为左端点的最大区间和,共 个,加入大根堆。
priority_queue<pair<pair<int, int>, int> > q;
for(int i = 1; i <= n; i++) q.push(make_pair(t[root[i]].mx, i));
cpp之后每次取出后,利用线段树找到另一个最大的。
int ans;
while(k--) {
pair<pair<int, int>, int> now = q.top(); q.pop();
ans = now.first.first;
add(root[now.second], root[now.second], 1, n, now.first.second, now.first.second, -inf);
q.push(make_pair(t[root[now.second]].mx, now.second));
}
cout << ans << endl;
cppCode#
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
int ret = 0, ch = getchar(), f = 1;
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch <= '9' && ch >= '0') {
ret = ret * 10 + ch - '0';
ch = getchar();
}
return ret * f;
}
const int MAXN = 1e5 + 10;
const int inf = 0x3f3f3f3f3f3f3f3fll;
int n, k;
int in[MAXN], pre_sum[MAXN];
int nxt[MAXN];
map<int, int> mp;
struct node{
pair<int, int> mx;
int add_mark, l, r;
} t[MAXN << 8];
int point_cnt = 0;
void push_up(int id) {
t[id].mx = max(t[t[id].l].mx, t[t[id].r].mx);
}
void build(int &id, int l, int r) {
id = ++point_cnt;
if(l == r) {
t[id].mx = make_pair(pre_sum[l], l);
return;
}
int mid = (l + r) >> 1;
build(t[id].l, l, mid);
build(t[id].r, mid + 1, r);
push_up(id);
}
int root[MAXN];
void add_point(int &id, int v) {
if(!id) return;
t[++point_cnt] = t[id]; id = point_cnt;
if(v == -inf) {
t[id].mx.first = t[id].add_mark = v;
} else {
t[id].mx.first += v; t[id].add_mark += v;
}
}
void push_down(int id) {
if(!t[id].add_mark) return;
if(t[id].l) add_point(t[id].l, t[id].add_mark);
if(t[id].r) add_point(t[id].r, t[id].add_mark);
t[id].add_mark = 0;
}
void add(int &id, int his, int l, int r, int L, int R, int v) {
id = ++point_cnt;
t[id].mx.first = -inf;
if(l == L && r == R) {
t[id] = t[his];
add_point(id, v);
return;
}
push_down(his);
int mid = (l + r) >> 1;
if(R <= mid) {
t[id].r = t[his].r;
add(t[id].l, t[his].l, l, mid, L, R, v);
} else if(L > mid) {
t[id].l = t[his].l;
add(t[id].r, t[his].r, mid + 1, r, L, R, v);
} else {
add(t[id].l, t[his].l, l, mid, L, mid, v);
add(t[id].r, t[his].r, mid + 1, r, mid + 1, R, v);
}
push_up(id);
}
signed main() {
n = read(); k = read();
for(int i = 1; i <= n; i++) in[i] = read();
for(int i = 1; i <= n; i++) nxt[i] = n + 1;
for(int i = n; i >= 1; i--) {
if(mp[in[i]]) nxt[i] = mp[in[i]];
mp[in[i]] = i;
}
mp.clear();
for(int i = 1; i <= n; i++) {
pre_sum[i] = pre_sum[i - 1];
if(!mp[in[i]]) {
pre_sum[i] += in[i];
mp[in[i]] = 1;
}
}
t[0].mx = make_pair(-inf-1, 0);
build(root[1], 1, n);
for(int l = 2; l <= n; l++) {
root[l] = root[l - 1];
add(root[l], root[l - 1], 1, n, l - 1, nxt[l - 1] - 1, -in[l - 1]);
add(root[l], root[l], 1, n, l - 1, l - 1, -inf);
}
priority_queue<pair<pair<int, int>, int> > q;
for(int i = 1; i <= n; i++) q.push(make_pair(t[root[i]].mx, i));
int ans;
while(k--) {
pair<pair<int, int>, int> now = q.top(); q.pop();
ans = now.first.first;
add(root[now.second], root[now.second], 1, n, now.first.second, now.first.second, -inf);
q.push(make_pair(t[root[now.second]].mx, now.second));
}
cout << ans << endl;
return 0;
}
cpp