AcWing100.增减序列

题目

给定一个长度为 n n n 的数列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an,每次可以选择一个区间 [ l , r ] [l,r] [l,r],使下标在这个区间内的数都加一或者都减一。

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

输入格式

第一行输入正整数 n n n

接下来 n n n 行,每行输入一个整数,第 i + 1 i+1 i+1 行的整数代表 a i a_i ai

输出格式

第一行输出最少操作次数。

第二行输出最终能得到多少种结果。

数据范围

  • 0 < n ≤ 1 0 5 00<n105
  • 0 ≤ a i < 2147483648 0≤a_i<2147483648 0ai<2147483648

输入样例

4
1
1
2
2

输出样例

1
2

分析

求出 a a a 的差分序列 b b b,其中 b 1 = a 1 , b i = a i − a i − 1 ( 2 ≤ i ≤ n ) b_1 = a_1,b_i = a_i - a_{i-1} (2 \le i \le n) b1=a1bi=aiai12in。令 b n + 1 = 0 b_{n+1} = 0 bn+1=0

题目对序列 a a a 的操作,相当于每次选出 b 1 , b 2 , . . . , b n + 1 b_1, b_2, ..., b_{n+1} b1,b2,...,bn+1 中的任意两个数,一个加1,另一个减1。目标是把 b 2 , b 3 , . . . , b n b_2, b_3,..., b_n b2,b3...,bn 变为全0。最终得到的数列 a a a 就是由 n n n b 1 b_1 b1 构成的。

b 1 , b 2 , . . . , b n + 1 b_1, b_2, ..., b_{n+1} b1,b2,...,bn+1 中任选两个数的方法可分为四类。

  1. b i b_i bi b j b_j bj,其中 2 ≤ i , j ≤ n 2 \le i, j \le n 2i,jn。这种操作会改变 b 2 , b 3 , . . . , b n b_2, b_3, ..., b_n b2,b3,...,bn 中两个数的值。应该保证 b i b_i bi b j b_j bj 一正一负的前提下,尽量多地采取这种操作,更快地接近目标。
  2. b 1 b_1 b1 b j b_j bj,其中 2 ≤ j ≤ n 2 \le j \le n 2jn
  3. b i b_i bi b n + 1 b_{n +1} bn+1,其中 2 ≤ i ≤ n 2 \le i \le n 2in
  4. b 1 b_1 b1 b n + 1 b_{n + 1} bn+1。这种情况没有意义,因为它不会改变 b 2 , b 3 , . . . , b n b_2, b_3, ..., b_n b2,b3,...,bn 的值(原序列 a a a 的每个数都进行了相同的操作,所以差分没变),相当于浪费了一次操作,一定不是最优解。

b 2 , b 3 , . . . , b n b_2, b_3, ..., b_n b2,b3,...,bn 中正数总和为 p p p,负数总和为的绝对值为 q q q。首先以正负数配对的方式尽量执行第 1 类操作,可执行 m i n ( p , q ) min(p, q) min(p,q) 次。剩余 ∣ p − q ∣ |p - q| pq 个未配对,每个可以选与 b 1 b_1 b1 b n + 1 b_{n + 1} bn+1 配对,即执行第 2 类或第 3 类操作,共需 ∣ p − q ∣ |p - q| pq 次。

综上所述,最少操作次数为 m i n ( p , q ) + ∣ p − q ∣ = m a x ( p , q ) min(p, q) + |p - q| = max(p, q) min(p,q)+pq=max(p,q) 次。根据 ∣ p − q ∣ |p - q| pq 次第2、第 3 类操作的选择情况,能产生 ∣ p − q ∣ + 1 |p - q| + 1 pq+1 种不同的 b 1 b_1 b1 的值(原本的 b 1 b_1 b1 是一种),即最终得到的序列 a a a 可能有 ∣ p − q ∣ + 1 |p - q| + 1 pq+1 种。

代码

#include 
#include 
using namespace std;

#define ll long long

const int N = 100010;

ll a[N]; //原数组

int main() {
    int n;
    
    cin >> n;
    
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    
    //求差分序列:倒着求,因为要用到i-1位置的值,而该值是原数组的值
    for (int i = n; i > 1; i--) a[i] -= a[i - 1];
    
    //因为数组中值比较大,可能会溢出,所以使用long long
    //pos正数和,neg负数和
    ll pos = 0, neg = 0;
    //求拆分数组中所有正数的和 和 所有负数的和
    for (int i = 2; i <= n; i++) {
        if (a[i] > 0) pos += a[i];
        else neg -= a[i];
    }
    
    cout << min(pos, neg) + abs(pos - neg) << endl;
    cout << abs(pos - neg) + 1 << endl;
    return 0;
}

你可能感兴趣的:(算法竞赛进阶指南,#,AcWing,差分,贪心)