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的下标,最后再对差分数组进行最大值求和;