首页 > 其他分享 >AtCoder Regular Contest 066 F Contest with Drinks Hard

AtCoder Regular Contest 066 F Contest with Drinks Hard

时间:2023-10-16 21:36:24浏览次数:44  
标签:node AtCoder 066 top Contest mid stk maxn &&

洛谷传送门

AtCoder 传送门

下文令 \(a\) 为原题中的 \(T\)。

考虑若没有饮料,可以设 \(f_i\) 表示,考虑了前 \(i\) 道题,第 \(i\) 道题没做的最大得分。转移就枚举上一道没做的题 \(j\),那么 \([j + 1, i - 1]\) 形成一个连续段。设 \(b_i\) 为 \(a_i\) 的前缀和,可得:

\[f_i = \max\limits_{j = 0}^{i - 1} \{ f_j + \frac{(i - j)(i - j - 1)}{2} - b_{i - 1} + b_j \} \]

拆项可得:

\[f_i - \frac{i(i - 1)}{2} + b_{i - 1} = \max\limits_{j = 0}^{i - 1} \{ f_j + \frac{j(j + 1)}{2} + b_j - ij \} \]

容易发现其为斜率优化形式,\(f_i - \frac{i(i - 1)}{2} + b_{i - 1}\) 是截距,\(f_j + \frac{j(j + 1)}{2} + b_j\) 是纵坐标,\(i\) 是斜率,\(j\) 是横坐标。注意到斜率和横坐标都单增,所以需要使用单调栈维护上凸包。

考虑若有饮料,设饮料把 \(a_x\) 变成 \(y\)。分类讨论是否使用饮料。再求一个 \(g_i\) 表示考虑了第 \(i\) 道题到第 \(n\) 道题,第 \(i\) 道题没做的最大得分。

若没使用饮料,答案是 \(f_x + g_x\);

若使用饮料,答案是 \(h_x + a_x - y\)。其中 \(h_i\) 表示必须做第 \(i\) 道题的最大得分。

考虑如何求 \(h_i\)。有一个单次 \(O(n^2)\) 的求法:

\[h_i = \max\limits_{l < i < r} \{ f_l + g_r + \frac{(r - l)(r - l - 1)}{2} - b_{r - 1} + b_l \} \]

套路地,考虑分治。设当前分治区间为 \([L, R]\),\(mid = \left\lfloor\frac{L + R}{2}\right\rfloor\)。考虑 \(l \in [L, mid], r \in [mid + 1, R]\),对 \(i \in (L, R)\) 造成的贡献。分类讨论 \(i\) 在哪半边,例如若 \(i \in (L, mid], l \in [L, i - 1], r \in [mid + 1, R]\),就求一个 \([mid + 1, R]\) 的凸包,然后遍历 \(l\),双指针遍历凸包即可。

时间复杂度为 \(O(n \log n)\)。

code
// Problem: F - Contest with Drinks Hard
// Contest: AtCoder - AtCoder Regular Contest 066
// URL: https://atcoder.jp/contests/arc066/tasks/arc066_d
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = 300100;

ll n, m, a[maxn], b[maxn], f[maxn], g[maxn], h[maxn], stk[maxn], top;
struct node {
	ll x, y;
	node(ll a = 0, ll b = 0) : x(a), y(b) {}
} c[maxn];

inline node operator + (const node &a, const node &b) {
	return node(a.x + b.x, a.y + b.y);
}

inline node operator - (const node &a, const node &b) {
	return node(a.x - b.x, a.y - b.y);
}

inline ll operator * (const node &a, const node &b) {
	return a.x * b.y - a.y * b.x;
}

void dfs(int l, int r) {
	if (l == r) {
		return;
	}
	int mid = (l + r) >> 1, tot = 0, top = 0;
	for (int i = mid + 1; i <= r; ++i) {
		c[++tot] = node(i, g[i] + 1LL * i * (i - 1) / 2 - b[i - 1]);
	}
	for (int i = 1; i <= tot; ++i) {
		while (top > 1 && (c[i] - c[stk[top - 1]]) * (c[stk[top]] - c[stk[top - 1]]) <= 0) {
			--top;
		}
		stk[++top] = i;
	}
	ll mx = -1e18;
	// printf("l, r: %d %d %d\n", l, mid, r);
	for (int i = l, j = top; i <= mid; ++i) {
		while (j > 1 && (c[stk[j]].y - c[stk[j - 1]].y) <= (c[stk[j]].x - c[stk[j - 1]].x) * i) {
			--j;
		}
		int k = stk[j] + mid;
		h[i] = max(h[i], mx);
		// printf("%d %d %lld\n", i, k, f[i] + g[k] + 1LL * (k - i) * (k - i - 1) / 2 - b[k - 1] + b[i]);
		mx = max(mx, f[i] + g[k] + 1LL * (k - i) * (k - i - 1) / 2 - b[k - 1] + b[i]);
	}
	tot = top = 0;
	for (int i = l; i <= mid; ++i) {
		c[++tot] = node(i, f[i] + 1LL * i * (i + 1) / 2 + b[i]);
	}
	for (int i = 1; i <= tot; ++i) {
		while (top > 1 && (c[i] - c[stk[top - 1]]) * (c[stk[top]] - c[stk[top - 1]]) <= 0) {
			--top;
		}
		stk[++top] = i;
	}
	mx = -1e18;
	for (int i = r, j = 1; i > mid; --i) {
		while (j < top && (c[stk[j + 1]].y - c[stk[j]].y) >= (c[stk[j + 1]].x - c[stk[j]].x) * i) {
			++j;
		}
		int k = stk[j] + l - 1;
		h[i] = max(h[i], mx);
		mx = max(mx, f[k] + g[i] + 1LL * (i - k) * (i - k - 1) / 2 - b[i - 1] + b[k]);
	}
	dfs(l, mid);
	dfs(mid + 1, r);
}

void solve() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", &a[i]);
		b[i] = b[i - 1] + a[i];
	}
	scanf("%lld", &m);
	stk[++top] = 0;
	for (int i = 1; i <= n; ++i) {
		while (top > 1 && (c[stk[top]].y - c[stk[top - 1]].y) <= (c[stk[top]].x - c[stk[top - 1]].x) * i) {
			--top;
		}
		int j = stk[top];
		f[i] = f[j] + 1LL * (i - j) * (i - j - 1) / 2 - b[i - 1] + b[j];
		c[i] = node(i, f[i] + 1LL * i * (i + 1) / 2 + b[i]);
		while (top > 1 && (c[i] - c[stk[top - 1]]) * (c[stk[top]] - c[stk[top - 1]]) <= 0) {
			--top;
		}
		stk[++top] = i;
	}
	reverse(a + 1, a + n + 1);
	for (int i = 1; i <= n; ++i) {
		b[i] = b[i - 1] + a[i];
	}
	mems(stk, 0);
	top = 0;
	stk[++top] = 0;
	for (int i = 1; i <= n; ++i) {
		while (top > 1 && (c[stk[top]].y - c[stk[top - 1]].y) <= (c[stk[top]].x - c[stk[top - 1]].x) * i) {
			--top;
		}
		int j = stk[top];
		g[i] = g[j] + 1LL * (i - j) * (i - j - 1) / 2 - b[i - 1] + b[j];
		c[i] = node(i, g[i] + 1LL * i * (i + 1) / 2 + b[i]);
		while (top > 1 && (c[i] - c[stk[top - 1]]) * (c[stk[top]] - c[stk[top - 1]]) <= 0) {
			--top;
		}
		stk[++top] = i;
	}
	reverse(a + 1, a + n + 1);
	reverse(g + 1, g + n + 1);
	for (int i = 1; i <= n; ++i) {
		b[i] = b[i - 1] + a[i];
	}
	mems(h, -0x3f);
	dfs(0, n + 1);
	// for (int i = 1; i <= n; ++i) {
		// printf("h[%d] = %lld\n", i, h[i]);
	// }
	// for (int i = 1; i <= n + 1; ++i) {
		// printf("g[%d] = %lld\n", i, g[i]);
	// }
	while (m--) {
		ll x, y;
		scanf("%lld%lld", &x, &y);
		printf("%lld\n", max(f[x] + g[x], h[x] + a[x] - y));
	}
}

int main() {
	int T = 1;
	// scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}

标签:node,AtCoder,066,top,Contest,mid,stk,maxn,&&
From: https://www.cnblogs.com/zltzlt-blog/p/17768396.html

相关文章

  • [Leetcode Weekly Contest]367
    链接:LeetCode[Leetcode]2903.找出满足差值条件的下标I给你一个下标从0开始、长度为n的整数数组nums,以及整数indexDifference和整数valueDifference。你的任务是从范围[0,n-1]内找出2个满足下述所有条件的下标i和j:abs(i-j)>=indexDifference且a......
  • Atcoder Regular Contest 167
    卡B下大分了,怎么回事呢。A.ToastsforBreakfastParty发现题意是让方差尽可能小,就是让\(A\)里的值尽可能接近。所以从小到大排个序,把\(A_{N,\dots,N-M+1}\)依次放进\(1,2,\dots,M\),再把\(A_{N-M,\dots,1}\)依次放进\(M,M-1,\dots,2M-N+1\)就赢了。B.Productof......
  • AtCoder Beginner Contest 324
    在高铁上加训!A-Same(abc324A)题目大意给定\(n\)个数,问是否都相等。解题思路判断是不是全部数属于第一个数即可。或者直接拿set去重。神奇的代码#include<bits/stdc++.h>usingnamespacestd;usingLL=longlong;intmain(void){ios::sync_with_stdio(f......
  • 【题解】AtCoder-ARC167
    AtCoder-ARC167AToastsforBreakfastParty一定不会有空盘,问题转化成\(2m\)个数,其中\(2m-n\)个是\(0\),这样一定是最大值和最小值一起,次大值和次小值一起,以此类推。提交记录:Submission-AtCoderAtCoder-ARC167BProductofDivisors\(A^B=\prod_ip_i^{Bc_i}\),那么答案......
  • AtCoder Beginner Contest 180 F Unbranched
    洛谷传送门AtCoder传送门首先进行一个容斥,把连通块最大值\(=K\)变成\(\leK\)的方案数减去\(\leK-1\)的方案数。考虑dp,设\(f_{i,j}\)表示当前用了\(i\)个点,\(j\)条边。转移即枚举其中一个连通块的大小\(k\)。为了不算重,我们强制让这个连通块包含点\(1\),类......
  • AtCoder Beginner Contest 324
    D-SquarePermutation须知:最大的平方数的平方一定小于等于10n,平方数最多为10(n/2)(因为再大会越界)因为要求的数一定是原数的排列组合,所以它们的元素和对应的元素个数一定相同所以只要判断平方数的字符串是否与原字符串相等即可(这里可以利用排序判断)点击查看代码#include<bi......
  • AtCoder Beginner Contest 324 DF题题解
    比赛链接D-SquarePermutation其实比较简单,但是比赛时候脑子不转了,竟然在尝试枚举全排列,然后算了一下复杂度直接不会做了。正解应该是枚举完全平方数,底数枚举到\(sqrt(10^{14})\)即可,因为n最大为13。然后统计一下这个完全平方数各个数字出现了多少个,和读入的比较一下是......
  • Atcoder Beginner Contest 324 F Beautiful Path 题解-分数规划
    为了更好的阅读体验,请点击这里分数规划小技巧:尽可能将式子写成存在某种取值,使得不等式成立的形式。不然可能需要绕几个弯才能想出来。题目链接题目大意:给出一个DAG,每条边有一个\(b_i,c_i\),保证从编号小的边向编号大的边连边,且\(1\)到\(n\)必有路径,求\(1\)到\(n\)......
  • 2022 China Collegiate Programming Contest (CCPC) Guilin Site(持续更新)
    Preface由于还有两周就要滚去打区域赛了,这周开始周末每天都训一场吧这场总体来说打的还可以,虽然E题这个Easy从卡局卡到3h,但由于其它的题都是一遍过所以罚时还尚可跻进Au区后面一个小时看徐神和祁神苦战K大分类讨论,虽然场下感觉摸了一个B的做法出来,但感觉实现还是太麻烦了就没写......
  • AtCoder Beginner Contest 321 C-321-like Searcher
    可以观察到0-9的所有子集都能恰组成一个满足题目条件的数字,所以共有1022个数{除空集和0}方法就是二元枚举,找出所有数然后排序。#include<iostream>#include<cstdio>#include<vector>#include<algorithm>usingnamespacestd;usingll=longlong;vector<ll>v;sig......