hihocoder#1046: K个串
[传送门]
这种区间内相同数字只能被统计一次/只有区间内数字都不相同才对答案有贡献的题都可以用扫描线扫右端点,表示当前区间右端点为$r$。然后当前线段树/树状数组维护区间左端点为$[1,r)$时对应的答案,那么新加一个数对区间$[last[a[r] + 1, r]$多了$a[r]$的贡献。
这道题也一样。只是因为对于每一个版本的线段树都得用到,所以用了可持久化线段树来维护,第$i$个版本就是右端点为$i$的版本。新加一个数只比上一个版本多了一段区间修改,那么可以用lazy标记来做,注意,这里的lazy可以不下放,因为对答案有贡献的是全局最大值,下传标记没必要(主要也是不会能下传lazy的可持久化线段树。
然后每次都把全局最大值放进优先队列,优先队列里取出最大值,并把该位置删掉,加入该版本下的次大值。重复$K$次即可。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5 + 7; const int M = 2e7 + 7; const ll INF = 0x3f3f3f3f3f3f3f3f; struct Node { ll mx; int pos; bool operator < (const Node &rhs) const { return mx < rhs.mx; } }; struct Seg { int lp[M], rp[M]; ll lazy[M]; Node tree[M]; int tol; void update(int &p, int q, int l, int r, int x, int y, ll val) { p = ++tol; if (q) tree[p] = tree[q]; lp[p] = lp[q], rp[p] = rp[q]; lazy[p] = lazy[q]; if (x <= l && y >= r) { lazy[p] += val; tree[p].mx += val; if (l == r) tree[p].pos = l; return; } int mid = l + r >> 1; if (x <= mid) update(lp[p], lp[q], l, mid, x, y, val); if (y > mid) update(rp[p], rp[q], mid + 1, r, x, y, val); tree[p] = max(tree[lp[p]], tree[rp[p]]); tree[p].mx += lazy[p]; } } seg; int root[N]; ll a[N]; priority_queue< pair<Node, int> > que; map<int, int> last; int main() { //printf("%d\n", sizeof(seg) / 1024 / 1024); int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); seg.tree[0].mx = -INF; for (int i = 1; i <= n; i++) { seg.update(root[i], root[i - 1], 1, n, i, i, 0); seg.update(root[i], root[i], 1, n, last[a[i]] + 1, i, a[i]); last[a[i]] = i; que.push(pair<Node, int>(seg.tree[root[i]], i)); } ll ans = 0; while (k--) { Node temp = que.top().first; int pos = temp.pos, id = que.top().second; que.pop(); ans = temp.mx; seg.update(root[id], root[id], 1, n, pos, pos, -INF); que.push(pair<Node, int>(seg.tree[root[id]], id)); } printf("%lld\n", ans); return 0; }