给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
输入格式
第一行输入正整数n。
接下来n行,每行输入一个整数,第i+1行的整数代表ai。
输出格式
第一行输出最少操作次数。
第二行输出最终能得到多少种结果。
数据范围
0<n≤105,
0≤ai<2147483648
输入样例:
4
1
1
2
2
输出样例:
1
2
题意:如题 |
思路(差分+贪心):
一开始看到区间修改,想到线段树和树状数组,但是这个题目并没有区间和查询的操作,而且也明确是+1或者-1 , 并且最后只需要输出最少的操作次数和种类。所以这题可以直接从差分的角度切入。
要使得最后所有数都相等,换句话说,就是每个元素之间的差值都为0 。 那我们就用一个b数组,b[i] = a[i] - a[i-1],这样,我们在修改【L,R】区间的时候(+1),只需要b[L]++,b[R+1]- - ,而其他都不变。为什么呢?对于【L,R】区间内的每个点,你加了1之后,他们之间的差分当然还是不变的,而区间左边界的点和前一个元素的差值(a[L] - a[L-1])就多加了1,右边界和后面一个点的差值(a[R+1]-a[R])就减少了1 。 (如果是-1就反过来)
所以,问题就变成了选取i,j两点,且2<=i,j<=n ,对b[i]- -, b[j] ++ ,最终使得所有b都变成0(也就是所有a[i]相等了)。
接下来便是贪心的思想,我尽量每次选择的每对b[i] 、b[j]都是异号的。为什么?若b[i]负,b[j]正,负的b[i]++,正的b[j]- -,才能尽快地使得所有b变成0嘛(反之亦然)。那么操作次数,就会由正负对个数决定。但是负数和正数的个数不一定相等啊,会有多出来的正数或者负数怎么办呢?这时候我们拿它和b[1]凑, 若剩下是正数,b[1]++,b[j]- -, 代表【1,j-1】这个区间都加上1。剩下都是负数,就b[1]- -, b[j]++,就是把这个区间都-1,这样就使得b[j]往0靠拢。那么剩下的次数就是多出来的正数或者负数的值。
因为序列b中值对移动的次数贡献都是其本身的大小(因为只能+1 或者-1,所以每次使得b[i]变为0移动次数就是其绝对值),那么设所有正数的和为pos , 所有负数的和的绝对值为neg,那么移动的次数就是
ans = min(pos,neg) + abs(pos-neg) = max(pos,neg)。第一个等式中前者是正负匹配的对数,后者是剩下来的正数或者负数(用来和b[1]匹配)。
而最后产生的结果种数,就只和我剩下来和b[1]匹配的值有关了。b[1]可以配对加(减)0,1,2,3…|pos-neg| ,即abs(pos-neg)+1。
AC代码:
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include <queue>
#include<sstream>
#include <stack>
#include <set>
#include<vector>
#define FAST ios::sync_with_stdio(false)
#define abs(a) ((a)>=0?(a):-(a))
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define