@loj - 3039@ 「JOISC 2019 Day4」蛋糕拼接 3
@description@
@solution@
首先考虑选定蛋糕后,如何编排美观度最大,因为编排只会影响 \(-\sum_{j=1}^{M}|C_{k_j} - C_{k_{j+1}}|\) 的大小,所以只需要它最大即可。
考虑每个 C 的贡献,可以将上式写作 \(-\sum_{j=1}^{M}(a_j + b_j)*C_{k_j}\),其中 \(a_j, b_j\) 为 1 或 -1,是通过拆绝对值得到的。
由于 a, b 是拆绝对值得到的,所以最大值对应的 a = b = 1,最小值对应的 a = b = -1;同时 ∑(a+b) = 0。
我们可以证明,除最大值与最小值以外,其他数取 a = 1, b = -1 最优。假如更改一个数的 b = 1,则必然有一个比它小的数的 a 更改为 -1,而这是不优的。
所以可以等价地转为使 \(\sum_{j=1}^{M}V_{k_j} - 2*(\max\{C_{k_j}\} - \min\{C_{k_j}\})\) 最大。
考虑将所有蛋糕按 C 排序。然后我们去枚举最大值的上界 C[r] 和最小值的下界 C[l](如果是枚举最大值则要保证那个值必须选择,后面的处理就不大方便)。
则我们选择的 M 块蛋糕要在区间 [l, r] 中选择。因为 C 已经固定,我们需要 V 的和最大,显然是选择区间内前 M 大的 V 求和。
则一个区间 [l, r] 的贡献 = 区间内 V 值前 M 大之和 - 2*(C[r] - C[l])。枚举区间并计算需要 O(n^2*logn) 的时间复杂度。
然后考虑优化。我们可以从决策单调性的方面入手。因为考虑了一会儿发现没啥地方可以下手的,于是生成一点随机数据打个表惊讶地发现它有决策单调性。
如果知道了它有决策单调性,用单调栈+二分或者分治找决策点都可以,用可持久化权值线段树计算,时间复杂度降为 O(n*log^2n)。
大概感性证明一下单调性。我们只需要证明如果 p < q <= i 且区间 [q, i] 优于 [p, i],则区间 [q, i+1] 优于 [p, i+1]。
记 f(l, r) 表示区间 [l, r] 内 V 值前 M 大之和。由题设可得 f(q, i) - 2*(C[i] - C[q]) >= f(p, i) - 2*(C[i] - C[p]),即 f(q, i) - f(p, i) >= 2*C[p] - 2*C[q]。
因为 f(l, r) 有一个性质:对于 p < q <= i,有 f(q, i+1) - f(q, i) >= f(p, i+1) - f(p, i)。这个性质直观上感知不难,详细证明需要讨论区间 [p, i], [q, i] 中的最小值与 V[i+1] 的关系。
于是 f(q, i+1) - f(p, i+1) >= f(q, i) - f(p, i) >= 2*C[p] - 2*C[q],从而得到结论 f(q, i+1) - 2*(C[i+1] - C[q]) >= f(p, i+1) - 2*(C[i+1] - C[p])。
@accepted code@
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 200000;
const ll INF = (1LL<<60);
struct segtree{
struct node{
node *ch[2];
int cnt; ll sum;
}pl[20*MAXN + 5], *rt[MAXN + 5], *ncnt, *NIL;
segtree() {rt[0] = ncnt = NIL = &pl[0]; NIL->ch[0] = NIL->ch[1] = NIL;}
node *insert(node *pre, int l, int r, int p, ll k) {
node *nw = (++ncnt); (*nw) = (*pre);
nw->cnt++, nw->sum += k;
if( l == r ) return nw;
int mid = (l + r) >> 1;
if( p <= mid ) nw->ch[0] = insert(pre->ch[0], l, mid, p, k);
else nw->ch[1] = insert(pre->ch[1], mid + 1, r, p, k);
return nw;
}
ll query(node *L, node *R, int p) {
if( !p ) return 0;
if( R->ch[0] == NIL && R->ch[1] == NIL )
return R->sum/R->cnt*p;
if( R->ch[1]->cnt - L->ch[1]->cnt > p )
return query(L->ch[1], R->ch[1], p);
else
return query(L->ch[0], R->ch[0], p - (R->ch[1]->cnt - L->ch[1]->cnt)) + R->ch[1]->sum - L->ch[1]->sum;
}
}T;
struct cake{
int V, C;
friend bool operator < (cake a, cake b) {
return a.C < b.C;
}
}ck[MAXN + 5];
int N, M;
ll get_ans(int l, int r) {
return T.query(T.rt[l-1], T.rt[r], M) - 2*(ck[r].C - ck[l].C);
}
ll ans = -INF; int pnt[MAXN + 5];
int get_point(int l, int r, int x) {
int p = l; ll k = get_ans(p, x);
for(int i=l+1;i<=r&&x-i+1>=M;i++) {
ll q = get_ans(i, x);
if( q > k )
p = i, k = q;
}
ans = max(ans, k);
return pnt[x] = p;
}
void solve(int L, int R, int le, int ri) {
if( L > R ) return ;
int mid = (L + R) >> 1, p = get_point(le, ri, mid);
solve(L, mid - 1, le, p);
solve(mid + 1, R, p, ri);
}
int d[MAXN + 5], dcnt = 0;
int main() {
scanf("%d%d", &N, &M);
for(int i=1;i<=N;i++)
scanf("%d%d", &ck[i].V, &ck[i].C), d[++dcnt] = ck[i].V;
sort(ck + 1, ck + N + 1);
sort(d + 1, d + dcnt + 1), dcnt = unique(d + 1, d + dcnt + 1) - d - 1;
for(int i=1;i<=N;i++)
T.rt[i] = T.insert(T.rt[i-1], 1, N, lower_bound(d + 1, d + dcnt + 1, ck[i].V) - d, ck[i].V);
solve(M, N, 1, N - M + 1);
printf("%lld\n", ans);
}
@details@
好久没有写可持久化权值线段树求第 k 大(我写过吗),结果没有处理好存在相同的值的时候怎么办。。。WA 了好几发。。。
然后就是注意区间 [l, r] 的长度必须 >= M,不然也会 WA。。。