csp2021-09-2 非零段划分

CSP真题解 非零段划分


前言

请添加图片描述
请添加图片描述

一、70分,超时

#include <iostream>
#include <algorithm>

#include <bits/stdc++.h>
using namespace std;

const int N = 15;

int a[N], b[N],c[N];
int main()
{
    int n,nmax=0,mmax=0,p=0;
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
         if(a[i]>=nmax)
            nmax=a[i];

    } //存储数组,并且找到元素的最大值
    a[0]=a[n+1]=0; //边界赋0;易于计算
	int k=unique(a,a+n+2)-a-1; // 去重,去掉相邻的重复元素
	/*for(int i = 1;i<k;i++)
        printf("%d ",a[i]);
                printf("\n");*/

    for(int i=1;i<=nmax;i++)  //p从1开始取到最大值nmax
    {
        for(int j = 1;j<k;j++)
        {
            if(a[j]<i)
                a[j]=0;
        }  //对小于p的元素置0
        k=unique(a,a+k+1)-a-1;//再次去掉相邻重复元素
        /*for(int w = 1;w<k;w++)
        printf("%d ",a[w]);
        printf("\n");*/
        for(int q = 0;q<k;q++)  //计算非零段个数等同于数数组中0的个数
        {
            if(a[q]==0)
                p++;

        }
        if(p>=mmax) //记录最大值
            mmax=p;
        p=0;
    }
   printf("%d", mmax);//输出最大值

    return 0;
}



题解思想:
对于数组中的元素,其实相邻的重复元素是可以看成一个元素的,为什么呢?因为p小于该元素,那它们就都会存在,并且是一个整体,如果p大于该元素,又都变成了0,所以相邻的重复元素可以看成一个元素。
去掉重复元素有什么好处?
请接着往下看

0 5 1 20 10 10 10 10 15 10 20 1 5 10 15
初始数组的元素值,a [0]赋0,容易计算边界值

0 5 1 20 10 15 10 20 1 5 10 15
第一次去重后,中间四个10 变成一个了,有1个0,1个非零段

0 5 0 20 10 15 10 20 0 5 10 15
p取2所以数组中的1变成0了,3个0,3个非零段

0 20 10 15 10 20 0 10 15
p取6时,5就变成了0
原来的0 5 0 20 10 15 10 20 0 5 10 15
变成了0 0 0 20 10 15 10 20 0 0 10 15
去掉相邻重复元素
变成了0 20 10 15 10 20 0 10 15 2个0,2个非零段

0 20 0 15 0 20 0 15
p取11时,10就变成了0 4个0,4个非零段

0 20 0 20
p取16时,15就变成了0, 2个0,2个非零段

好处就是,去重之后的数组,只需要数一下数组中有多少个0就有多少个非零段
非常好理解的思想,而且还有优化的空间
例如:

  • p的取值,可以不是从1到最大值,可以看出这个例子中,真正有用的是p=2,6,11,16
  • 可以建立索引,每次置0的时候,就不用遍历数组了,用空间换时间

请添加图片描述

二、差分思想

前缀和

什么是前缀和:例如一个数组:a[1],a[2],a[3]…a[n],前缀和S[i]表示的是该数组的前i项的和,例如S[3] = a[1] + a[2] + a[3],S[i] = a[1] + a[2] + a[3] + … + a[i - 1] + a[i]。 即前n项和

差分

差分是什么

差分可以看做前缀和的逆运算,同前缀和一样,差分数组从1号位置点开始,假设我们有一个原数组a:a[1], a[2], a[3] … a[i],我们需要构造出一个数组b:b[1], b[2], b[3] … b[i],使得 a[i] = b[1] + b[2] + b[3] + … + b[i]即数组a是数组b的前缀和数组,即每一个a[i] 都可以对b数组从 1 ~ i 求前缀和得到
请添加图片描述

代码如下(示例):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 500010;
 
int a[N], b[N];
 
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        if(a[i] > a[i - 1])
        {
            b[a[i - 1]] ++;
            b[a[i]] --;
        }
    }
    int ans = 0, t = 0;
    for(int i = 0;i < N;i ++ )
    {
        t += b[i];
        ans = max(ans, t);
    }
    cout << ans << endl;
    
    return 0;
}

题解思想:
其实你会发现你知道了差分,你知道了前缀和,也还是不会应用到题目里,甚至可能连代码都看不懂,
首先你先看看这个
请添加图片描述
理解一下差分是在干什么

#include <stdio.h>


int main()
{

    int n,m; // 序列长n,m次插入操作
    int l,r,c; //插入区间与插入的值
    scanf("%d%d",&n,&m);
    int a[n],b[n];  //也可以写全局变量
    for(int i=1;i<=n;i++)

        scanf("%d", &a[i]); // 输入的序列值存入数组a
        a[0]=0;
        b[0]=0;
    for(int i = 1;i<=n;i++)  // 构造差分数组
        b[i]=a[i]-a[i-1];
    for(int i=0;i<m;i++){

        scanf("%d %d %d",&l,&r,&c);
        b[l]+=c;
        b[r+1]-=c;           //只要改变差分数组的两个值,就可以改变原数组区间内的所有值
    }
    for(int i =1;i<=n;i++)
        b[i]+=b[i-1];    //差分数组自己前n项相加得到原数组
    for(int i =1;i<=n;i++)
        printf("%d ",b[i]);


}

如果你想对一个数组的某一个区间内所有元素加上一个值,只需要对差分数组,在边界值加减一次即可

  if(a[i] > a[i - 1])
        {
            b[a[i - 1]] ++;
            b[a[i]] --;
        }

再回看这里,这里干了个什么事呢?
就是 如果a[i]>a[i-1],也就是后一个数比前一个大,那么,当p取到它们中间的值时,就会出现一个非零段;而当p比 a[i]大的时候,就会都变成0,没有非零段;
例如a[1] >a[0] 当p取0、1、2时
--------- 3 > 0
都会出现一个非零段,但当p取3时,就都变成0,没有非零段
b[0] = 1 b[3] = -1
b[0]从0变为1,为什么?p=0的时候,有1个非零段,同理p=1、2 、3也只有一个非零段,只用对b[0]加1就行了,为什么只加一个0呢?因为差分数组只需要改变一个值,就可以影响一个区间的值,为什么又要把b[3]从0变为-1呢,因为p>3的时候,3也变成0了,没有非零段了
实际上就是从1到n持续循环这个过程,按着数组a的元素的值,定为差分数组b的下标,最后再对差分数组进行最大值求和;

  • 20
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值