Codeforces 1477E - Nezzar and Tournaments(分析性质+线段树二分)

Codeforces 题目传送门 & 洛谷题目传送门

1477 可还行(My rating is 1477.

部分思路借鉴了 ymx 鸽鸽的题解,因为现在好像全网除了官方题解只有这一篇题解。

首先考虑对于一种固定的安排选手顺序的方案 \(c\),如何求遍历到第 \(i\) 个选手时的得分。如果不考虑对 \(0\)\(\max\) 的限制,那显然就是 \(K+(c_2-c_1)+(c_3-c_2)+\cdots+(c_i-c_{i-1})=K+c_i-c_1\)。但由于每次得分对 \(0\)\(\max\) 这一机制的存在,对于得分小于 \(0\) 的时刻,归零这一操作相当于给得分加上了当前得分的相反数,也就是说,真实得分其实是不小于我们算出来的得分 \(K+c_i-c_1\) 的,那么我们究竟应该如何计算真实得分比我们算出来的得分大多少呢?考虑取出下标 \(\le i\) 且值最小的 \(c_j\),那么总的增量就是使得 \(K+c_j-c_1\) 变得 \(\ge 0\) 的最大增量,即 \(\max(c_1-K-c_j,0)\)。因此我们有 \(d_i=K+c_i-c_i+\max(c_1-K-(\min\limits_{j\le i}c_j),0)\)

注意到上面的式子中,\(c_1\) 起一个类似于主元的作用,因此考虑将其拎出来,不妨假设其值已经确定,如何安排后面位置上的值。显然对于 \(b\) 中的数而言,我们希望后面的 \(\max\) 越小越好,因此我们会将其降序排序,同理,对于 \(a\) 中的数而言,我们希望后面的 \(\max\) 越大越好,因此我们会将其升序排序,至于 \(a,b\) 的相对顺序关系……我们肯定希望 \(b\) 尽量排在前面,\(a\) 尽量排在后面,因此我们肯定会先安排 \(b\) 再安排 \(a\),即假设去掉 \(c_1\)\(a\) 升序排序的结果为 \(p\),长度为 \(n’\)\(b\) 降序排序的结果为 \(q\),长度为 \(m’\),那么最优方案的 \(c\) 序列将会是 \(q_1,q_2,\cdots,q_{m’},p_1,p_2,\cdots,p_{n’}\)

接下来考虑带上 \(c_1\) 的限制,考虑什么样的 \(c_1\) 可能成为最优解。假设 \(f(t)\) 表示 \(c_1\) 出自 \(a\) 序列且 \(=t\) 的最大分数差,\(g(t)\) 表示 \(c_1\) 出自 \(b\) 序列且 \(=t\) 的最大分数差,那么:

\[\begin{aligned} f(t)&=k-\sum\limits_{i=1}^m(k-t+b_i+\max(t-k-b_i,0))+\sum\limits_{i=1}^n(k-t+a_i+\max(t-k-M,0))-(k-t+t-\max(t-k-M,0)\\ &=(k-t)·(n-m)+\sum\limits_{i=1}^na_i-\sum\limits_{j=1}^mb_i-\sum\limits_{i=1}^m\max(t-k-b_i,0)+\max(t-k-M,0)·(n-1)\\ &=t·(m-n)-\sum\limits_{i=1}^m\max(t-k-b_i,0)+\max(t-k-M,0)·(n-1)+(k·(n-m)+\sum\limits_{i=1}^na_i-\sum\limits_{j=1}^mb_i) \end{aligned} \]

其中 \(M=\min(\min\limits_{i=1}^na_i,\min\limits_{j=1}^mb_j)\)

考虑将与 \(t\) 有关的项提出来并画出 \(f(t)\) 关于 \(t\) 的函数图像,那么应是先一段斜率为 \(m-n\) 的折线,然后在 \(k+M\) 处斜率突增 \(n-1\),变为 \(m-1\),再后来,每 \(\ge\) 一个 \(k+b_i\) 斜率都会 \(-1\),最后斜率变为 \(-1\)。大致图像如下图所示(图片来自 CF 官方题解):

不难发现最优解要么在最左端取到,要么在倒数第二段线段的两个断点处取到,因此只要暴力计算这三个值的 \(f\) 后取 \(\max\) 即可算出 \(a\) 中元素作为 \(c_1\) 的最大分数差。

接下来考虑 \(g\),式子推法基本一样:

\[\begin{aligned} g(t)&=-k-\sum\limits_{i=1}^m(k-t+b_i+\max(t-k-b_i,0))+\sum\limits_{i=1}^n(k-t+a_i+\max(t-k-M,0))-(k-t+t-\max(t-k-t,0))\\ &=-k+(k-t)·(n-m)+\max(t-k-M,0)·n-\sum\limits_{i=1}^m\max(t-k-b_i,0)+\sum\limits_{i=1}^na_i-\sum\limits_{i=1}^mb_i\\ &=t·(m-n)+\max(t-k-M,0)·n+\sum\limits_{i=1}^m\max(t-k-b_i,0)+(k·(n-m-1)+\sum\limits_{i=1}^na_i-\sum\limits_{i=1}^mb_i) \end{aligned} \]

和上面的折线形态基本一致,只不过少了最后斜率为 \(-1\) 的部分,因此我们只用 check \(b\) 中最小值和最大值即可。

至于怎么计算 \(f(t),g(t)\),注意到式子大部分都至于 \(k,t,n,m,M,\sum a_i,\sum b_i\) 有关,都可以 \(\mathcal O(1)\) 求得,唯独 \(\sum\limits_{i=1}^m\max(t-k-b_i,0)\) 与序列 \(b\) 的具体数值有关,这个可以通过对 \(b\) 建权值线段树轻松搞定。

时间复杂度 \((n+q)\log n\)

const int MAXN = 2e5;
const int MAXV = 1e6;
const int MAXP = MAXV * 4;
const int INF = 0x3f3f3f3f;
const ll INFll = 0x3f3f3f3f3f3f3f3fll;
int n, m, qu, a[MAXN + 5], b[MAXN + 5];
multiset<int> stA, stB; ll sumA, sumB;
struct node {int ch[2], siz; ll sum;} s[MAXP + 5];
int rt, ncnt;
void insert(int &k, int l, int r, int p, int v) {
	if (!k) k = ++ncnt; s[k].siz += v; s[k].sum += p * v;
	if (l == r) return; int mid = l + r >> 1;
	(p <= mid) ? insert(s[k].ch[0], l, mid, p, v) : insert(s[k].ch[1], mid + 1, r, p, v);
}
pair<int, ll> query(int k, int l, int r, int ql, int qr) {
	if (!k || ql > qr) return mp(0, 0);
	if (ql <= l && r <= qr) return mp(s[k].siz, s[k].sum);
	int mid = l + r >> 1;
	if (qr <= mid) return query(s[k].ch[0], l, mid, ql, qr);
	else if (ql > mid) return query(s[k].ch[1], mid + 1, r, ql, qr);
	else {
		auto pl = query(s[k].ch[0], l, mid, ql, mid);
		auto pr = query(s[k].ch[1], mid + 1, r, mid + 1, qr);
		return mp(pl.fi + pr.fi, pl.se + pr.se);
	}
}
void insA(int v) {sumA += v; stA.insert(v);}
void insB(int v) {sumB += v; stB.insert(v); insert(rt, 0, MAXV, v, 1);}
void delA(int v) {sumA -= v; stA.erase(stA.find(v));}
void delB(int v) {sumB -= v; stB.erase(stB.find(v)); insert(rt, 0, MAXV, v, -1);}
ll ask(int k, int t, int opt) {
	int M = INF;
	if (!stA.empty()) chkmin(M, *stA.begin());
	if (!stB.empty()) chkmin(M, *stB.begin());
	ll res = opt * k + 1ll * (k - t) * (n - m) + sumA - sumB;
//	printf("! ask %d %d %d %lld\n", k, t, M, res);
	res += 1ll * max(t - k - M, 0) * n;
	auto P = query(rt, 0, MAXV, 0, t - k);
	res -= 1ll * (t - k) * P.fi - P.se;
	return res;
}
ll queryA(int k, int t) {delA(t); --n; ll res = ask(k, t, 1); insA(t); ++n; return res;}
ll queryB(int k, int t) {delB(t); --m; ll res = ask(k, t, -1); insB(t); ++m; return res;}
ll query(int k) {
	ll res = -INFll;
	chkmax(res, queryA(k, *stA.begin()));
	if (stB.size() >= 2) {
		int v = (*++stB.rbegin()) + k;
		multiset<int> :: iterator it = stA.lower_bound(v);
		int A = -1, B = -1;
		if (it != stA.end()) A = *it;
		if (it != stA.begin()) B = *--it;
		if (~A) chkmax(res, queryA(k, A));
		if (~B) chkmax(res, queryA(k, B));
	}
	chkmax(res, queryB(k, *stB.begin()));
	chkmax(res, queryB(k, *stB.rbegin()));
	return res;
}
int main() {
	scanf("%d%d%d", &n, &m, &qu);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), insA(a[i]);
	for (int i = 1; i <= m; i++) scanf("%d", &b[i]), insB(b[i]);
	while (qu--) {
		int opt; scanf("%d", &opt);
		if (opt == 1) {
			int p, x; scanf("%d%d", &p, &x);
			delA(a[p]); a[p] = x; insA(a[p]);
		} else if (opt == 2) {
			int p, x; scanf("%d%d", &p, &x);
			delB(b[p]); b[p] = x; insB(b[p]);
		} else {
			int k; scanf("%d", &k);
			printf("%lld\n", query(k));
		}
	}
	return 0;
}
posted @ 2022-02-25 23:32  tzc_wk  阅读(118)  评论(0)    收藏  举报