《算法竞赛进阶指南》—增减序列

目录

一.题目描述

二.题目分析

三.代码实现

1.求差分数组

2.检索到b[i]>0时

3.检索到b[i]<0时

四.算法优化


一.题目描述

给定一个长度为 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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值