【AcWing 100】 增减序列 差分+贪心 详解

给定一个长度为 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
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值