假如给我们一个序列,并计算他们的差分数组
则假设p为差分数组(不包括b[1],从b[2]开始)中正值的总和 q为差分数组中负值的总和
P=4 q=0
先给出公式,最小操作数为min(p,q)+|p-q|,方案数为1+|p-q|
下面详细解释
关于最小操作数
关于方案数目
1.首先,有四个操作
这里j是作为右端点,改变的是元素[i,j-1]中所有的元素,在差分数组的角度是b[i]+-1,b[j]+-1;
情况1: 2<=i,j<=n,选b[i]和b[j]
这样会改变b[2],b[3]…b[n]中两个数的值,尽量多用(最好) b[l]+=c b[r+1]-=c
情况2:i=1,2<=j<=n 选b[j]和b[1],对某个前缀进行操作 这样子会改变b1,b1+-1
情况3: 2<=i<=n,j=n+1 对某个后缀进行操作 这样子会改变b[n],bn+-1
情况4:i=1,j=n+1 对某个序列都进行操作(直接不用)
2.min(p,q)解析
根据贪心的思想,我们要让数组中所有元素相同的话,就是要让b[2~n]都变为0,这样子a[2~n]就都是a[1]了
假设p为差分数组(不包括b[1],从b[2]开始)中正值的总和 q为差分数组中负值的总和
在b数组中选择两个数,2<=i<j<=n 其中b[i]<0(b[i]>0) ,b[j]>0(b[j]<0), b[i]b[j]异号
注意是选择2<=i<j<=n的两个点,也就是第一个操作(贪心的思想)
注意,这里先不选择b[1]和b[n+1],因为p,q没有记录他们的差分值
选择的意义在于
根据差分的定义,我们每一次修改区间[l,r],都是b[l]+=1,b[r+1]-=1(或者相反)
那么在差分数组选择的i,j-1就是左右端点,我们贪心的选择一正一负,正数不断--,负数不断++,直到有一个为0,就换一个端点,这样子就是最小操作数
3.|p-q|解析
这些还需要填(挖)的坑的次数恰好是|p-q| p-q的绝对值
为什么是|p-q|,因为我们是选择2到n的一个点作为左端点++(--),另一个点作为右端点--(++)
由于p,q是记录差分数组[2~n]所有的值,我们每次操作都只会让一个正数--,一个负数++,他们进行的操作次数是一样的,进行min(p,q)操作后,在数组[2~n]中值不为0的总和一定为|p-q|或者-|p-q|
这时候剩下的点就在[2~n]中,我们选择一个和b[1]或者b[n+1]配对,也就是进行第二种或者第三种操作,由于剩下的值是|p-q|...那么就只要进行|p-q|次+1或者-1操作就行了
下面是一个案例
先进行第一种操作
我们先选择 b[2] 和 b[4] 进行3次+操作,然后b[4]=0,就换成 b[2] 和 b[3] 进行一次+操作,这样子[2~4]的b数组值都是0,(注意这里进行的是区间修改,[L,R]的点都被++)
发现还有一个b[5]<0,那么就选择他和6进行++操作
这时候对于b[2]~b[n]的点都操作完毕了,但是还有1个点b[5]没有满足条件
就进行第二或第三种操作
选择1或n+1作为一个端点,没有满足条件的作为另一个端点操作
那就选择 b[5] 和 b[7] (n+1=6+1=7) 最后进行一次++操作
方案数=1+|p-q|解析
方案数表示的是情况2,3的分配
方案数的验证
数组和差分数组
可能的方案
1. 选了 3次 3 操作,把bn 减1减了3次
2.选了 2次 3 操作,1次 2 操作
2.选了 1次 3 操作,2次 2 操作
2.选了 0次 3 操作,3次 2 操作
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=100005;
int a[N],b[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i]-a[i-1];
}
long long q=0,p=0;
for(int i=2;i<=n;i++)
{
if(b[i]<0) p-=b[i];
else q+=b[i];
}
//pq可能很大需要开longlong ,min(p,q)+|p-q|可以转为max(p,q)
cout<<max(p,q)<<endl;
cout<<1+(int)fabs(p-q);
//注意fabs返回值是浮点值,需要转化为int
}