@description@
今天是 IOI 酱的生日,所以她的哥哥 JOI 君给她预定了一个生日蛋糕。虽然他计划买一整个蛋糕,但是他不小心订成了
N
块蛋糕。这
N
块蛋糕编号为
1\ldots N
,每块蛋糕都有价值和颜色。第
i
块蛋糕的价值为
V_i
,颜色深度为
C_i
。
为了做成一整块蛋糕,他决定选择
M
块互不相同的蛋糕,然后将它们按一定顺序排成一个环。整块蛋糕的美观程度定义如下:
\sum_{j=1}^M V_{k_j}-\sum_{j=1}^M|C_{k_j}-C_{k_{j+1}}|
其中,他选择了编号为
k_1,\ldots ,k_M
的蛋糕(这里令
k_{M+1}=k_1
)。换句话说,整个蛋糕的美观程度为选择蛋糕的价值和与所有相邻两块蛋糕颜色深度差的绝对值之和的差。JOI 君想要让整块蛋糕尽可能美观。
写一个程序,在给定蛋糕的块数,选择蛋糕的数目和每块蛋糕的价值和颜色深度的情况下,计算 JOI 君做成的蛋糕的最大美观度。
@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。。。