题解:P12569 [UOI 2023] An Array and Partial Sums

题意

给定一个长度为 \(n\) 的序列 \(a\)。你可以进行以下三种操作:

  • \(a\) 中所有元素取相反数。
  • 选择 \(a\) 的一个子段,替换成其前缀和数组。
  • 选择 \(a\) 的一个子段,替换成其后缀和数组。

求最少的操作次数使得 \(a\) 中所有元素非负,并给出一个合法的操作序列。\(1\leq n\leq 2\times 10^5\)\(-1\leq a_i\leq 1\)

题解

分类讨论题。

显然答案不会很大。我们首先指出答案不超过 \(3\)

证明:直接构造证明。设前缀和数组 \(pre_i=\sum\limits_{j=1}^ia_j\),令 \(x,y\) 分别表示 \(pre\) 的最靠前的最小值和最大值的位置。我们按 \(x,y\) 的大小关系讨论:

  • \(x=y\):说明 \(pre\) 中所有元素相同,于是 \(a_1=pre_1\)\(a_2=a_3=\cdots=a_n=0\)。若 \(a_1\geq 0\) 则无需操作,否则执行 \(1\) 次操作 \(1\) 即可。
  • \(x>y\):我们执行操作 \(1\),化为 \(x<y\) 的情况。
  • \(x<y\):我们先对 \(a[x+1,n]\) 执行操作 \(2\),那么被操作的数 \(a_i\leftarrow pre_i-pre_x\),一定是非负的。然后我们发现,在操作后的新的 \(pre'\) 数组中,\(pre'_y\) 一定是前缀最大值。这是因为

    \[\begin{align*} pre'_y&=pre_x+\sum_{i=x+1}^y(pre_i-pre_x)\\ &=pre_y+\sum_{i=x+1}^{y-1}(pre_i-pre_x)\\ &\geq pre_y \end{align*} \]

    因此对于 \(1\leq i\leq x\)\(pre'_i=pre_i\leq pre_y\leq pre'_y\)。而对于 \(x<i\leq y\),显然在这段区间内 \(pre'\) 单调不减,于是同样有 \(pre'_y\geq pre_i\)。那么我们只需再对 \([1,y]\) 执行操作 \(3\) 即可。被操作的数 \(a_i\leftarrow pre'_y-pre'_i\),必然是非负的。

\(\Box\)

接下来我们对答案 \(ans\) 进行分类讨论。

\(ans=0\)

此时 \(a\) 中所有元素已经非负了。

\(ans=1\)

首先显然判掉 \(a\) 中所有元素非正的情况,此时只需执行 \(1\) 次操作 \(1\)

否则我们一定是选择了一个区间 \([l,r]\),对 \(a[l,r]\) 执行操作 \(2/3\)。注意到合法的区间 \([l,r]\) 必然满足 \(a[1,l-1]\)\(a[r+1,n]\) 中的元素都非负,而我们把这些元素包含到操作区间里必然不劣,所以我们一定是执行全局操作。那么判断 \(a\) 的前缀和/后缀和数组是否非负即可。

\(ans=2\)

我们有可能执行了 \(1\) 次操作 \(1\),这种 case 化成 \(ans=1\) 的情况即可。

接下来考虑最后一次操作,不失一般性地,我们设最后一次执行了操作 \(2\)。根据 \(ans=1\) 的 case 的分析,这次操作我们一定是执行全局操作。所以问题转化为,能否在选择一个子段 \(a[l,r]\) 执行操作 \(2/3\) 后,使得 \(a\) 的前缀和数组非负。

我们来分类讨论第一次操作的类型。

若第一次操作是操作 \(2\),和前面类似,可以发现我们必然是对一个前缀执行操作。因为合法的区间 \([l,r]\) 要求 \(pre[1,l-1]\) 非负,所以我们把这部分包含到操作区间里同样不劣。

枚举操作的前缀 \([1,i]\),用线段树维护前缀和数组,每次 \(i\leftarrow i+1\) 就是 \(a\) 上的单点修改,对应于线段树上的后缀加,然后查询全局最小值判断是否合法即可。

接下来是比较复杂的 case:第一次操作是操作 \(3\)。设 \(pre\) 的后缀和数组 \(S_i=\sum\limits_{j=i}^npre_j\),再设操作区间为 \([l,r]\)。那么被操作的数 \(a_i\leftarrow pre_r-pre_{i-1}\)。分段考察新的 \(pre'\) 的取值:

  • \(1\leq i<l\)\(pre'_i=pre_i\)
  • \(l\leq i\leq r\):化一下式子:

    \[\begin{align*} pre'_i&=pre_{l-1}+\sum_{j=l}^i(pre_r-pre_{j-1})\\ &=(i-l+1)pre_r-\sum_{j=l+1}^{i}pre_{j-1}\\ &=(i-l+1)pre_r-S_l+S_i \end{align*} \]

  • \(r<i\leq n\):利用 \(l\leq i\leq r\) 推的式子,不难得到

    \[\begin{align*} pre'_i&=(r-l+1)pre_r-S_l+S_r+pre_i-pre_r\\ &=(r-l+1)pre_r-S_l+S_{r+1}+pre_i \end{align*} \]

根据这些讨论刻画怎样的区间 \([l,r]\) 是合法的:

\[\begin{cases} \min\limits_{i=1}^{l-1}pre_i\geq 0\\ (l-1)pre_r+S_l\leq \min\limits_{i=l}^r\{ipre_r+S_i\}\\ (l-1)pre_r+S_l\leq rpre_r+S_{r+1}+\min\limits_{i=r+1}^npre_i \end{cases} \]

考虑枚举右端点 \(r\),尝试找到合法的 \(l\) 与之匹配。

对于第 \(1\) 个条件,我们找到最小的 \(p\),使得 \(pre_p<0\),限制 \(l\leq p\) 即可。

对于第 \(3\) 个条件,我们把每个可能的 \(l\) 视作一条直线 \(y=(l-1)x+S_l\),那么每次只需查询 \(x=pre_r\)\(y\) 的最小值。显然可以用李超线段树维护。而注意到每次插入的直线斜率单调递增,实际上可以直接使用单调栈维护上凸壳,二分查询最小值。

难搞的是第 \(2\) 个条件,因为右边的 \(\min\)\(l,r\) 都有关。我们先假设 \(l\leq p\leq r\),令 \(f(i)=ipre_r+S_i\),把第二个条件拆成

\[\begin{cases} f(l)-pre_r\leq \min\limits_{i=l}^{p-1}f(i)\\ f(l)-pre_r\leq \min\limits_{i=p}^rf(i) \end{cases} \]

对于前者,一个显然的必要条件是 \(pre_r\geq 0\),然后不难看出取 \(l=\operatorname*{arg\,min}\limits_{i=1}^{p-1}f(i)\) 一定合法。注意这里的 \(l\) 和前面处理第 \(3\) 个条件时得到的 \(l\)一样的,不需要额外维护。

对于后者,不等式右侧只与 \(r\) 有关,因此此时同样取 \(l=\operatorname*{arg\,min}\limits_{i=1}^{p-1}f(i)\) 一定是最优的。因此同样使用单调栈维护上凸壳求出 \(\min\limits_{i=p}^rf(i)\),判断是否合法即可。

如果 \(r<p\),我们直接忽略拆出来的第二个条件即可。

这样我们就处理完了所有条件。

\(ans=3\)

用前文中的方法构造即可。


时间复杂度 \(\mathcal{O}(n\log{n})\)

代码

这里只给出 \(ans=2\) 且两次执行相同操作部分的代码。其他部分是很好实现的。

struct Line {
	int id;
	ll k, b;
	ll operator()(ll x) const { return k * x + b; }
};
inline bool check(Line a, Line b, Line c) { return (a.b - b.b) * (c.k - b.k) > (b.b - c.b) * (b.k - a.k); }
inline bool check(Line a, Line b, ll x) { return (b.k - a.k) * x <= a.b - b.b; }
struct Convex {
	int top;
	Line stk[N];
	inline void init() { top = 0; }
	inline void ins(Line l) {
		while (top >= 2 && !check(stk[top - 1], stk[top], l)) --top;
		stk[++top] = l;
	}
	inline Line query(ll x) {
		int l = 1, r = top;
		while (l < r) {
			int mid = l + r + 1 >> 1;
			if (mid == 1 || check(stk[mid - 1], stk[mid], x)) l = mid;
			else r = mid - 1;
		}
		return stk[l];
	}
} c1, c2;

inline bool solve2(bool flag) {
	for (int i = n; i; --i) S[i] = S[i + 1] + pre[i];
	suf[n + 1] = INF;
	for (int i = n; i; --i) suf[i] = min(suf[i + 1], pre[i]);
	int p = 0;
	for (int i = 1; i <= n; ++i) if (pre[i] < 0) { p = i; break; }
	c1.init(), c2.init();
	for (int r = 1; r <= n; ++r) {
		if (r <= p) c1.ins({r, r - 1, S[r]});
		if (r >= p) c2.ins({r, r, S[r]});
		if (pre[r] < 0) continue;
		Line res = c1.query(pre[r]);
		int id = res.id;
		ll val = res(pre[r]), mn = (ll)r * pre[r] + S[r + 1] + suf[r + 1];
		if (r >= p) chk_min(mn, c2.query(pre[r])(pre[r]));
		if (val <= mn) {
			if (!flag) op[++sz] = {3, id, r}, op[++sz] = {2, 1, n};
			else op[++sz] = {2, n + 1 - r, n + 1 - id}, op[++sz] = {3, 1, n};
			return print(), 1;
		}
	}
	return 0;
}
posted @ 2025-08-25 15:57  P2441M  阅读(16)  评论(0)    收藏  举报