### Day8:1.202109-2 2.202112-2
#### 1.202109-2:非零段划分(岛屿问题,差分数组(差分思想,不是广义的差分)+前缀和)
(1)未想出来,尝试0和1的结果骗分失败
(2)学习:
一.**岛屿问题(凸峰和凹谷,利用差分数组,结果利用前缀和(实际上为后缀和))(好理解)**:
1.考虑p足够大的情况,这时所有的岛都被海水淹没了,只有0个岛屿
然后海平面逐渐下降,岛屿数量开始变化
每当一个凸峰出现,岛屿数就会多一个;
每当一个凹谷出现,原本相邻的两个岛屿就被这个凹谷连在一起了,岛屿数减少一个
可以使用数组cnt[],**cnt[i] 表示海平面下降到i时,岛屿数量的变化**
这样,数组元素cnt[i]中存储的就是该元素被替换为0时,划分数变化的差分值
最大值则只需要从其前缀和(程序中实际为 后缀和)中找出最大值
**本题的差分数组**:像是一种**计数器**,指**某个数值引起的段数的变化**,通过计算每个数值为阈值时,非零段的**变化量(不是最终量)**。然后,通过对这个数组的前缀和(**状态变化的累积思想**)操作(**计算最终量**),我们能得出不同阈值时的非零段总数。
2.学习了unique的使用
[[Unique函数]]
**代码**:
```
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
int maxNum=-1;
vector<int> va(n+2,0);
va[0]=va[n+1]=0;
for(int i=1;i<=n;i++){
cin>>va[i];
if(va[i]>maxNum){
maxNum=va[i];
}
}
vector<int> vcnt(maxNum+1,0);
//使用unique去重
n=unique(va.begin(),va.end())-va.begin();
for(int i=1;i<n;i++){
//差分数组构造
if(va[i-1]<va[i] && (va[i]>va[i+1] && i<n-1)){
vcnt[va[i]]++;
}
if(va[i-1]>va[i] && (va[i]<va[i+1] && i<n-1)){
vcnt[va[i]]--;
}
}
int res=0;
int sum=0;
for(int i=maxNum;i!=0;i--){
//使用前缀和思想(实际上为后缀和)计算变化总值
sum+=vcnt[i];
res=max(sum,res);
}
cout<<res;
return 0;
}
```
**二.差分数组+前缀和(更能体现差分思想)**:
(1):**差分思想**:通过记录从何时开始增加一个新的非零段以及何时结束一个非零段来计算不同阈值下的段数变化。
如果 a[i]>a[i−1]
意味着当p取到 a[i−1]+1到 a[i]之间的值时,非零段+1
使用数组cnt[],cnt[i] 表示p从i-1上升到i时,非零段数量的变化
从**正向前缀和**中找出最大值就是所要的结果
(2)代码:
```
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
int maxNum=-1;
vector<int> va(n+1,0);
for(int i=1;i<=n;i++){
cin>>va[i];
if(va[i]>maxNum){
maxNum=va[i];
}
}
vector<int> vcnt(maxNum+2,0);
n=unique(va.begin(),va.end())-va.begin();
for(int i=1;i<n;i++){
if(va[i]>va[i-1]){
vcnt[va[i-1]+1]++;//非零段区间起点
vcnt[va[i]+1]--;//非零段区间终点
}
}
int res=0;
int sum=0;
for(int i=1;i<=maxNum;i++){
sum+=vcnt[i];
res=max(sum,res);
}
cout<<res;
return 0;
}
```
#### 2.202112-2:序列查询新解(分段乘法代替枚举加法)
(1)第一遍:70分(暴力枚举)
```
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n,N;
cin>>n>>N;
long long error=0;
vector<int> va(n+2,0);
for(int i=1;i<=n;i++){
cin>>va[i];
}
va[n+1]=N;
long long r=N/(n+1);
vector<int> vf(N,0);
vector<int> vg(N,0);
for(int i=0;i<N;i++){
vg[i]=i/r;
}
int aindex=0;
for(int i=0;i<N;){
if(i<va[aindex+1]){
vf[i]=aindex;
i++;
}
else{
aindex++;
}
}
for(int i=0;i<N;i++){
error+=abs(vf[i]-vg[i]);
}
cout<<error;
return 0;
}
```
(2)学习:
**可以先按照 f 分段,再按照 g 分段**
**f(x)值为 i 的 x 的取值范围为 [a[i],a[i+1])**
再对该范围内的数依据 g 值的不同继续分段:设当前段的第一个 g(x) = g,那么 g 值不变的最后一个 x 的取值就是 (g+1)∗r−1,那么我们就可以求出这段区间共有 cnt 个数,因而这段区间对 error 的贡献就是 abs(f−g)∗cnt,x 每次增加的步长即为这段里数的个数
**这里要注意,当前这段的最后一个数不一定是 g(x)=g的最后一个数,因为 g 可能会横跨不同的 f,所以 last = min((g + 1) * r - 1, a[i + 1] - 1);**
**代码**:
```
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n,N;
cin>>n>>N;
vector<int> va(n+2,0);
for(int i=1;i<=n;i++){
cin>>va[i];
}
va[n+1]=N;
long long error=0;
int r=N/(n+1);
for(int i=0;i<n+1;i++){//按f(i)来分段
int cnt=0;
for(int j=va[i];j<va[i+1];j+=cnt){//每一段f(i)再按g(x)来分段
int g=j/r;
int maxg=(g+1)*r-1;//找规律
int last=min(maxg,va[i+1]-1);//注意这边取最小值
cnt=last-j+1;
error+=cnt*abs(i-g);
}
}
cout<<error;
return 0;
}
```