P4552 [Poetize6] IncDec Sequence 题解

原题链接

简要题意:

给定长度为 \(n\) 的序列 \(\{a_i\}\),每次操作可以将任一区间 \([l,r]\) 的所有数加一(或减一),求至少多少次操作可以让序列中所有数相同。并且在保证最小操作数的情况下求相同的数的可能个数(即末状态数)。

\(n \leq 10^5\).

区间操作往往可以转换为差分操作。

注意到,如果我们令 \(\{c_i\}\)\(\{a_i\}\) 的差分数组,也就是:

\[\begin{cases} c_1 = a_1 \\ c_k = a_k - a_{k-1} (\forall k \in [2,n]) \\ \end{cases}\]

那么,这个时候,对于 \(\{a_i\}\) 中,\([l,r]\) 区间的 \(+1\) 操作,可以简化为:

\[\begin{cases} c_{l} \leftarrow c_{l} + 1 \\ c_{r+1} \leftarrow c_{r+1} - 1 \\ \end{cases}\]

同理 \(-1\) 操作可以简化为:

\[\begin{cases} c_{l} \leftarrow c_{l} - 1 \\ c_{r+1} \leftarrow c_{r+1} + 1 \\ \end{cases}\]

同时我们发现,\(\{a_i\}\) 全相等时,\(\{c_i\}\)\(c_1 = a_1\) 外全为 \(0\),于是题意转为:

给定一个长度为 \(n\) 的序列 \(\{c_i\}\),每次操作可以选定任意的 \(l,r\) 并将 \(c_l\)\(c_r\) 一个加一,一个减一。求能使得 \(c_2 \cdots c_n\) 全部变为 \(0\) 的最少操作数,以及变化后 \(c_1\) 可能的值的个数。

这里,答案已经呼之欲出了。

很显然,我们应该尽量把一个负数 \(c_x\) 和一个正数 \(c_y\) 同时进行 “绝对值逼近 \(0\)” 的加减操作,通过 \(\min(c_x,c_y)\) 次可以得到 \(0\). 于是我们将所有正数的和记为 \(p\),负数的 绝对值 的和记为 \(q\),显然,想要消灭所有正数(或负数)需要 \(\min(p,q)\) 次。而对于剩下的数,我们可以将其和 \(c_1\) 进行加减操作。于是第一问答案为 \(\max(p,q)\),这是十分明显的。

考虑第二问。注意到,需要满足最小操作数,因此 “绝对值逼近 \(0\)” 的加减操作 是不可能改变 \(c_1\) 的。但是注意到,最后对于剩下的数,当只剩下一个数 \(c_n \not = 0\) 的时候(注意一定是 \(n\)),我们就可以不一定选定 \([1,x]\) 操作。可以直接选定 \([n,n+1]\) 进行操作(想象你变化原数组末位的时候,后面一位的差分值也会变),而不是改变 \(c_1\). 也只有在这个时候才能在保证最小操作数的情况下使得 \(c_1\) 有更多可能性。此时 \(|c_n| = |p-q|\) 显然。

于是第二问答案为 \(|p-q|+1\).

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

代码实现中,差分利用滚动一波过去了。

#include <bits/stdc++.h>
using namespace std;

const int N=1e5+1;
typedef long long ll;

int n;
ll p,q;

int main() {
	scanf("%d",&n);
	int x; scanf("%d",&x);
	for(int i=2;i<=n;i++) {
		int y; scanf("%d",&y);
		if(y>x) p+=y-x;
		else q+=x-y;
		x=y;
	}
	printf("%lld\n%lld\n",max(p,q),abs(p-q)+1);
	return 0;
}
简易的代码胜过复杂的说教。
原文地址:https://www.cnblogs.com/bifanwen/p/15733755.html