https://www.acwing.com/problem/content/102/
题意
对于给定的数列a,求通过区间加和区间减使其中所有数都一直的最小步数,并给出最小步数的所有方案。
思路
考虑差分数组操作。可以将这些操作分为四种,这里以i和j指代 l e f t left left和 r i g h t + 1 right+1 right+1
- i > 1 , j < = n i>1,j<=n i>1,j<=n 即对一区间进行加减操作,且这一区间并非前缀或后缀。
- i = 1 , j < = n i=1,j<=n i=1,j<=n 对数列的长为 j j j的前缀进行操作
- i > 1 , j = n + 1 i>1,j=n+1 i>1,j=n+1 对数列的长为 j j j的后缀进行操作
- i = 1 , j = n + 1 i=1,j=n+1 i=1,j=n+1 对整条序列进行操作
不难看出,最后一类操作并无意义,所以我们仅考虑前三种即可。
把数列a中所有数变得相同,这一目标可以转化为将数列a的差分数组
b
b
b变成如下形式
a
[
i
]
,
0
,
0
,
0
,
0
,
0
⋅
⋅
⋅
⋅
⋅
⋅
a[i],0,0,0,0,0······
a[i],0,0,0,0,0⋅⋅⋅⋅⋅⋅
这也就是说,数列a中的每一个元素都与第1个元素相同,因此差分为0。
考虑转化后的这一目标,我们可以使用两个变量 p o s i t i v e positive positive和 n e g a t i v e negative negative记录差分数组 b b b中正数的绝对值之和,以及负数的绝对值之和。并且在操作过程中,将正数向0减,将负数向0加。
考虑差分数组的操作,即 b [ l e f t ] + + , b [ r i g h t + 1 ] − − b[left]++,b[right+1]-- b[left]++,b[right+1]−−,可以将“将正数向0减”和“将负数向0加”的过程配对进行。
这也就是说,我们希望优先进行第1种操作,在数列a中选取异号的 a [ i ] a[i] a[i]和 a [ j ] a[j] a[j],使对正数的减和负数的加能够在一次操作中完成。
由此,我们推导出最小次数的公式
m
i
n
(
p
o
s
i
t
i
v
e
,
n
e
g
a
t
i
v
e
)
+
a
b
s
(
p
o
s
i
t
i
v
e
−
n
e
g
a
t
i
v
e
)
min(positive,negative)+abs(positive-negative)
min(positive,negative)+abs(positive−negative)
m i n ( p o s i t i v e , n e g a t i v e ) min(positive,negative) min(positive,negative)代表将加和减配对进行的过程。在这之后,差分数组 b b b中除第一位之外应只剩下正数或者负数。
a b s ( p o s i t i v e − n e g a t i v e ) abs(positive-negative) abs(positive−negative)代表将剩下的正数(或负数)归零处理。
接下来,我们考虑方案数的统计。
由于在之前的操作中,我们已经使得在差分数组b中除 b [ 1 ] b[1] b[1]之外只有正数或者负数,所以第1种操作将加减配对进行不再可行。我们转而考虑第2种和第3种操作。
第2或3种操作,实质上对应着与 b 1 b_1 b1或 b n + 1 b_{n+1} bn+1配对。而 a b s ( p o s i t i v e − n e g a t i v e ) abs(positive-negative) abs(positive−negative)次操作,对应着 a b s ( p o s i t i v e − n e g a t i v e ) + 1 abs(positive-negative)+1 abs(positive−negative)+1种 b 1 b_1 b1的取值。这也就是说,从不与 b 1 b_1 b1配对(配对0次)到每次都与 b 1 b_1 b1配对,共有 a b s ( p o s i t i v e − n e g a t i v e ) abs(positive-negative) abs(positive−negative)种情况。
代码中也可不必记录原数组而直接在原数组上进行差分,但为了保证与题解的良好对应,将其保留之。
#include <iostream>
#include <algorithm>
#include <cmath>
typedef long long ll;
using namespace std;
const int N = 100010;
ll a[N];
ll b[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i <= n; i++)
{
b[i] = a[i] - a[i - 1];
}
ll positive = 0;
ll negative = 0;
for (int i = 2; i <= n; i++)
{
if (b[i] > 0)
positive += b[i];
else
negative -= b[i];
}
cout << min(positive, negative) + abs(positive - negative) << endl;
cout << abs(positive - negative) + 1 << endl;
return 0;
}