--- title: "非零段划分" created: 2025-11-28 tags: - 算法 --- # 非零段划分 ## 题目 [非零段划分](https://www.acwing.com/problem/content/description/4010/) ![[image-39366553.png]] ## 思路分析 可以分三个方向考虑 (1):y总那样的 水从下往上涨 去分析每个点被淹没时 答案的变化 (2):反过来 水一开始在最高位 往下退 分析每个点露出时 答案的变化 (3):从每个点入手 依次加入每个点 利用差分的思想 分析答案变化 对于第一个方向 ![[image-d37e3273.png]] 我们可以发现 对于上涨后淹没的每个点 不外乎就4种状态 1.单调上升的(左边比它矮 右边比它高) 那么把它淹没 还是有一个岛(它右边的)只不过体积变小了而已 对答案没影响 2.单调下降的(左边比它高 右边比它矮)那么把它淹没 也是一样 还有左边一个岛 也只是左边这个岛的体积变小了罢了 3.突出(左右都比他矮) 那么把它淹没 原本它是一座岛 现在没了 答案-1 4.凹陷(左右都比他高) 那么把它淹没 原本连成一块的岛就变成了两块 答案+1 对于一种特殊情况 就是如果有很多相邻的 且高度一样的岛 我们无法通过上诉的只比较当前点与左右点来得到 其实可以发现 这些相邻的 高度一样的岛 完全可以看成一个岛 他们共进退、 那么就可以用unique进行一个去重 (unique只是去重相邻元素 不要误解 先sort再unqiue才会把所有重复去除 所以它完美适应该场景) ![[image-91d8f6db.png]] 那么 就可以处理每个点 通过判断它与相邻点的关系 来去对初始状态进行++或-- 最后求一次前缀和 在这个过程中用max维护一个最大值 就能得到答案 但是我们可以发现一个问题 初始条件是什么? 理想情况下 当水没涨时 所有的点都在水平面上 形成一个岛 是吧 但是 如果000呢 那不就是0座岛吗 101呢 两座? 这样去写的话 我们还得去对初始状态有个判断 太麻烦了 那么看第二种方式 水平面一开始在最高处 把所有的点都淹没 这样一来 初始状态就确定了 初始为0座岛 好像省事了很多哈 再往下分析 发现也不过是分为4种情况 ![[image-278d7b6c.png]] 1.单调上升的(左边比它矮 右边比它高) 那么当它暴露出来 还是有一个岛(它右边的)只不过体积变大了而已 对答案没影响 2.单调下降的(左边比它高 右边比它矮)那么把它暴露出来 也是一样 还有左边一个岛 也只是左边这个岛的体积变大了罢了 3.突出(左右都比他矮) 那么把它暴露出来 原本左右分割的两个岛 现在被它连在一起了 答案-1 4.凹陷(左右都比他高) 那么把它露出 原本没有的岛 现在变成了一座 答案+1 发现其实就是和前一种++,-- 颠倒一下罢了 然后最后的答案应该是求一遍后缀和 (从最后一位开始算 累加起来 过程中找max) 接下来看第三种方式 它利用差分的思想 依次看加入每个点后 对答案的影响 这种比较难想 ![[image-1ef563bd.png]] 首先对于第一个0 其实不需要处理 对于9 ![[image-eec06745.png]] 可以发现 9一加入的话 任何0~9的水平面都会有一个岛 再看下一个2 它是往下的 ![[image-692765a1.png]] 对答案并不构成影响 在0~9的水平面还是只有一个岛露出 再看下一个6 ![[image-5746565f.png]] 可以发现 若水平线在2~6 就会加一个岛露出 而0~2 6~9处并不受影响 还是由前面的1影响 我们可以发现一个规律 若出现该元素比上一个元素大 a[i]>a[i-1](呈单增趋势)时 答案会被影响 即出现一个新的峰 影响怎么表示呢 其实就是 在a[i-1]到a[i]处全加上一个1 那不就是差分吗 只需要在插入每个点的时候 判断与前一个点的关系 如果大于 就在差分数组的b[a[i-1]]处++ b[a[i]]处--即可(无需+1 也是左闭右开) 不信可以继续往后验证 ![[image-76e09d1c.png]] 我们可以把它抽象成这样: ![[image-459a97ec.png]] 得到一个差分数组 重新构造前缀和后 再维护找到一个max 那就是答案 ## 代码实现 **水面上涨** ```cpp #include using namespace std; const int N=5e5+10; int n; int h[N]; int cnt[10010]; int main() { cin>>n; int sum=0; for(int i=1;i<=n;i++){ cin>>h[i]; if(h[i]) sum=1; } n=unique(h+1,h+n+1)-(h+1); h[0]=1,h[n + 1] = 0; for(int i=1;i<=n;i++){ if(h[i-1]h[i] && h[i+1]>h[i]) cnt[h[i]]++; } //除了特殊的一开始就全0的情况 不管水平面涨不涨都没岛 //否则默认所有的山都露在水面上 为一座岛 //这里不好处理 得重新遍历 不妨放在读入时处理 int res=0; for(int i=0;i<10000;i++){ sum+=cnt[i]; res=max(res,sum); } cout< using namespace std; const int N=5e5+10; int n; int h[N]; int cnt[10010]; int main() { cin>>n; for(int i=1;i<=n;i++){ cin>>h[i]; } n=unique(h+1,h+n+1)-(h+1); h[0]=0,h[n + 1] = 0; for(int i=1;i<=n;i++){ if(h[i-1]h[i] && h[i+1]>h[i]) cnt[h[i]]--; } int sum=0,res=0; //构造后缀和 for(int i=10000;i>=0;i--){ sum+=cnt[i]; res=max(res,sum); } cout< using namespace std; const int N=5e5+10; int n; int h[N],b[N]; int main() { cin>>n; for(int i=1;i<=n;i++){ cin>>h[i]; if(h[i]>h[i-1]){ b[h[i-1]]++,b[h[i]]--; } } int sum=0,res=0;; for(int i=0;i<=10000;i++){ sum+=b[i]; res=max(res,sum); } cout<