luoguP7028 [NWRRC2017]Joker 题解
一道牛逼的题目。
上来先来一波推式子:
\[\begin{split}
S_n&=\sum_{i=1}^n w_i\\
&=\sum_{i=1}^n\begin{cases}\dfrac{a_i}{P}&a_i>0\\\dfrac{a_i}{|N|}&a_i<0\end{cases}\\
&=\frac{1}{P}\sum_{i=1}^na_i\ [a_i>0]+\frac{1}{|N|}\sum_{i=1}^na_i\ [a_i<0]
\end{split}
\]
然后我们令
\[\begin{split}
P_i&=\sum_{j=1}^ia_j\ [a_j>0]\\
N_i&=-\sum_{j=1}^ia_j\ [a_j<0]
\end{split}
\]
直接代入上式,可以得到:
\[\begin{split}
S_i&=\frac{P_i}{P_n}-\frac{N_i}{N_n}\\
&=\frac{1}{P_nN_n}\times(P_iN_n-N_iP_n)
\end{split}
\]
因为 \(\frac{1}{P_nN_n}\) 是固定的,所以现在的问题是最大化 \(P_iN_n-N_iP_n\) 。
发现 \(P_iN_n-N_iP_n\) 的形式非常符合向量的叉积运算,所以上面的这个可以看成是 \((P_i,\ N_i)\times(P_n,\ N_n)\) 。
认真思考后发现,对于 \(S_i\) 大小可能有贡献的点一定是在一个右下凸壳上,证明略。
那么现在的问题是:对于一个不断更新的点集,求这个点集的下凸壳。
本人太菜,不会 \(poly\) 的做法,所以退而求其次采用分块维护下凸壳。
预处理的地方非常简单,就是对于每个块分别求出下凸壳。
对于修改的操作,我们发现所谓的区间修改也就是后缀的修改,散块直接重构,整块打标记。
同时还有一种不需要打标记的方法,发现向量叉积运算有分配律,所以可以直接维护一个类似前缀和的东西,这样可以不用打标记。
具体的方法是在查询的时候累计之前块最后一个点的答案。
查询操作对于每一个块找到最有可能成为答案的位置,然后所有的块进行比较。
对于一个块我们怎么找到一个最优秀的点,方法是这样的:
发现对于一个下凸壳,斜率是不断递增的,所以可以利用这样的性质直接二分即可。
点击查看代码
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cctype>
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
using std::pair;
using std::make_pair;
#define pii pair<int, int>
#define mp make_pair
using ll = long long;
const int inf = 1e9;
const ll infll = 0ll + 1e18;
const int N = 5e4 + 5;
const int blen = 250;
#define int long long
int n, q, a[N], s1, s2;
int st[N], en[N], bel[N], bnum, sta[blen], tp;
int tu[blen][blen], top[blen];
//s1 -> + s2 -> -
struct Point {
int x, y;
Point (int _x = 0, int _y = 0) {x = _x; y = _y;}
friend Point operator+(const Point &p, const Point &q) {
return Point(p.x + q.x, p.y + q.y);
}
friend Point operator-(const Point &p, const Point &q) {
return Point(p.x - q.x, p.y - q.y);
}
friend int operator*(const Point &p, const Point &q) {
return 1ll * p.x * q.y - 1ll * p.y * q.x;
}
} p[N], A;
template <class T>
inline void read(T &a);
inline int cross(Point a, Point b, Point c) {
return (a - b) * (c - b);
}
inline void Build() {
Point now = Point(0, 0);
for (int i = 1; i <= bnum; i ++) {
tp = 0;
now = Point(0, 0);
for (int j = st[i]; j <= en[i]; j ++) {
if (a[j] > 0) now = now + Point(a[j], 0);
else now = now + Point(0, -a[j]);
p[j] = now;
while (tp > 1) {
if (cross(p[j], p[sta[tp - 1]], p[sta[tp]]) >= 0) tp --;
else break;
}
sta[++tp] = j;
}
top[i] = tp;
for (int j = 1; j <= tp; j ++)
tu[i][j] = sta[j];
}
return ;
}
inline void rebuild(int bl) {
tp = 0;
for (int i = st[bl]; i <= en[bl]; i ++) {
while (tp > 1) {
if (cross(p[i], p[sta[tp - 1]], p[sta[tp]]) >= 0) tp --;
else break;
}
sta[++tp] = i;
}
top[bl] = tp;
for (int i = 1; i <= tp; i ++)
tu[bl][i] = sta[i];
return ;
}
inline pii getans(int bl) {
int left = 1, right = top[bl], ret = right;
int *tt = tu[bl];
while (left <= right) {
int mid = (left + right) / 2;
if ((p[tt[mid]] - p[tt[mid + 1]]) * A >= 0) {
right = mid - 1;
ret = std::min(ret, mid);
} else left = mid + 1;
}
return mp(tt[ret], p[tt[ret]] * A);
}
inline int query() {
int ans = -inf, ansid = inf, ss = 0;
A = Point(s1, s2);
for (int i = 1; i <= bnum; i ++) {
pii chose = getans(i);
if (ss + chose.second > ans) {
ans = ss + chose.second;
ansid = chose.first;
}
ss = ss + p[en[i]] * A;
}
return ansid;
}
inline void modify(int id, int x) {
Point now = Point(0, 0);
for (int i = st[bel[id]]; i <= en[bel[id]]; i ++) {
if (a[i] > 0) now = now + Point(a[i], 0);
else now = now + Point(0, -a[i]);
p[i] = now;
}
rebuild(bel[id]);
}
signed main(void) {
read(n), read(q);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i <= n; i++) {
s1 += (a[i] > 0) ? a[i] : 0;
s2 += (a[i] < 0) ? -a[i] : 0;
}
bnum = (n + blen - 1) / blen;
for (int i = 1; i <= bnum; i ++) {
st[i] = (i - 1) * blen + 1;
en[i] = i * blen;
}
en[bnum] = n;
for (int i = 1; i <= bnum; i ++)
for (int j = st[i]; j <= en[i]; j ++) bel[j] = i;
Build(); printf("%d\n", query());
// for (int i = 1; i <= top[1]; i ++) printf("-> %d", tu[1][i]);
// printf("\n");
for (int test = 1, id, x; test <= q; test ++) {
read(id), read(x);
s1 -= (a[id] > 0) ? a[id] : 0;
s2 -= (a[id] < 0) ? -a[id] : 0;
s1 += (x > 0) ? x : 0; s2 += (x < 0) ? -x : 0;
a[id] = x;
modify(id, x);
printf("%d\n", query());
}
return 0;
}
template <class T>
inline void read(T &a) {
T s = 0, t = 1;
char c = getchar();
while (!isdigit(c)) {t = (c == '-') ? -1 : t; c = getchar();}
while (isdigit(c)) s = s * 10 + c - '0', c = getchar();
a = s * t;
}