目录
一.题目描述
给定一个长度为 n 的数列 a1,a2,…,an每次可以选择一个区间 [l,r]使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
二.题目分析
1.对于“在一个区间加一减一”操作,可以很容易联想到差分的内容,原数组为a,差分数组为b,那么在区间[l,r]加上常数c可以定义为:
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;
}
2.要使得数列中的所有数一样,即a1=a2=……=an,而a1=b1,a2=b1+b2,ai=b1+b2+……bi,那么会有bi=0(i≠1)。而在区间[l:r]加(减)一即为b[l]++,b[r+1]--,因而问题转化为,至少需要多少次操作,可以让b[i]全部为0(b[1])除外。
3.由上述分析,一次操作可以同时修改数组b中两个内容的值,一个值+1,一个值-1。因而很容易想到,我们可以遍历数组b,当检索到大于0的值b[i]时,找到下一个小于0的值b[j],可以用一步操作使得b[i]--,b[j]++,事实上,若取t=min(b[i],abs(b[j])),可以使得b[i]-=t,b[j]+=t,这时候至少有一个值被修改为了0.检索到小于0的值,则寻找下一个大于0的值,过程类似。
4.可以想到,最后我们会出现,b[i]中只剩下了正数或者负数,在这个情况,就会出现不同可能的数列。同时让数组b中两个值同时减去某个数是做不到的,因而若数组b中只剩下正数c,每一步有两种选择:(1)b[1]+=1,b[i]-=1 (2)b[i]-=1(相当于区间[i,n])
因而对于这一个正数c,需要c步才能化为0,而每一步有两种情况,对于b[1]来说,最小是b[1],最大是b[1]+c,因而有c+1种情况,若此时数组b中所有正数和为sum,那么此时数列就有sum+1种情况。b[i]中只剩下了负数的情况也是类似。
三.代码实现
定义point为当前第一个不为0的位置
1.求差分数组
//求差分数组,数组a的产生可以认为是,在[1,1]区间插入a1,在[2,2]区间插入a2……
for(int i=1;i<=n;i++){
int x;
cin>>x;
insert(i,i+1,x);//r=i+1的原因是,对insert算法进行了修改:b[r]-=c
}
2.检索到b[i]>0时
bool nonnegtive(int x){//大于0,要找到下一个不为
int sumnonngetive=0;
for(int i=x;i<=n;i++){
if(b[i]<0){
int t=abs(b[i]);
int sub=min(t,b[x]);
changed(x,i,-sub);
count+=sub;//直接执行sub次操作
if(b[x]==0) point=x+1;
return true;
}
else{
sumnonngetive+=b[i];
}
}
//如果只剩下我这个正数啦,只能-1,可以加到b1去,也可以后面所有的减掉去
ans=sumnonngetive+1;
count+=sumnonngetive;
point=-1;
}
3.检索到b[i]<0时
bool negtive(int x){
int sumnegtive=0;
int t=abs(b[x]);
for(int i=x;i<=n;i++){
if(b[i]>0){
int add=min(t,b[i]);
changed(x,i,add);
count+=add;
if(b[x]==0) point=x+1;
return true;
}
else{
sumnegtive+=abs(b[i]);
}
}
ans=sumnegtive+1;
count+=sumnegtive;
point=-1;
}
运行时间:601ms
四.算法优化
事实上,若我们用LL来记录b[2:n]中所有正数的和,用neg来记录b[2:n]中所有负数的和。若LL≥neg,操作次数即为LL,因为正数在减1的同时把负数消掉了,剩下的正数又需要count个操作,而数列的种数即为LL+neg+1(“剩下的正数”)。同样的,若neg≤LL,操作的次数为abs(neg),数列的种数为abs(neg)-LL+1。需要注意的是,次数可能会超过int的表示范围而变成负数,因而要用long类型。
#include<iostream>
#include<math.h>
using namespace std;
const int N=100010;
int a[N],b[N];//数组a,差分数组b
int n;
void changed(int l,int r,int c){//在区间[l,r-1]内加上常数c
b[l]+=c;
b[r+1]-=c;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int x;
cin>>x;
changed(i,i,x);
} //求好差分数组
long LL=0,neg=0;
for(int i=2;i<=n;i++){
if(b[i]>=0) LL+=b[i];
else neg+=abs(b[i]);
}
if(LL>=neg) cout<<LL<<endl<<LL-neg+1;
else cout<<neg<<endl<<neg-LL+1;
}
运行时间:541ms