一类差分题的思考方式及经典例题
问题描述
这类题目一般都会给定一个整数序列\(A\)和一个大定义域\(D\)。定义一个与序列元素有关的函数\(f(X)\),定义域是\(D\),值域是非负整数。
找到一个\(X\),使得\(f(X)\)最大。
思考方式
虽然\(D\)的范围可能很大,但是\(f(X) > 0\)的\(X\)可能不多。因此我们只需要知道哪些\(X\)的\(f(X) > 0\),且这些\(f(x)\)的值是多少即可。
这类题一般都会先考虑序列中元素对哪些\(X\)有贡献,一个元素会使得一个区间\([l, r]\)上的答案同增或同减一个常数(通常就是\(1\))。比如:当\(X \in [l, r]\),\(a_i\)为\(f(X)\)贡献了\(1\)。
因此可以使用差分维护这个过程,最后求前缀和并更新答案即可。
因此我们对于具体题目的考虑就是,什么样的元素会影响哪个区间。
典型例题
题意:\(A_1,A_2, \dots , A_n\)是一个由\(n\)个自然数(非负整数)组成的数组。我们称其中\(A_i, \dots ,A_j\)是一个非零段,当且仅当以下条件同时满足:\(1 \leq i \leq j \leq n\);对于任意的整数\(k\),若\(i \leq k \leq j\),则\(A_k > 0\);\(i=1\)或\(A_{i−1}=0\);\(j=n\)或\(A_{j+1}=0\)。现在我们可以对数组\(A\)进行如下操作:任选一个正整数\(p\),然后将\(A\)中所有小于\(p\)的数都变为\(0\)。试选取一个合适的\(p\),使得数组\(A\)中的非零段个数达到最大。若输入的\(A\)所含非零段数已达最大值,可取\(p=1\),即不对\(A\)做任何修改。
解题思路:在这道题中,\(f(X)\)表示非零段个数。我们现在考虑什么样的元素会影响哪个区间。对于相邻两个数,如果\(A_i > A_{i - 1}\),为了让\(A_i\)变为一个非零段的开始,我们需要把\(A_{i - 1}\)变为\(0\)。而\([A_{i-1}, A_i-1]\)可以满足这个要求,也就是为这个区间上的\(f(X)\)都贡献了\(1\)。因此,\(A_i > A_{i - 1}\)这样的元素会使得\([A_{i-1}, A_i-1]\)这个区间答案加\(1\)。
注:岛(https://www.acwing.com/problem/content/2016/ )与本题几乎一样,只不过定义域更大,因此需要用map存差分。
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 500010, M = 10010;
int n;
int a[N], b[M];
void insert(int l, int r)
{
b[l] ++, b[r + 1] --;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
int x;
scanf("%d", &x);
a[i] = x;
if(a[i] > a[i - 1]) insert(a[i - 1], a[i] - 1);
//如果选择[a[i - 1], a[i] - 1],那么第i个元素开始就称为了非零段
//因此区间内所有数字贡献+1
}
int ans = 0;
for(int i = 0; i < M; i ++) {
b[i] = b[i - 1] + b[i];
ans = max(ans, b[i]);
}
printf("%d\n", ans);
return 0;
}
- Robot Takahashi(https://atcoder.jp/contests/abc257/tasks/abc257_c)
题意:有一群大人和小孩,每个人都有一个高度\(A_i\)。你需要划定一个高度标注\(X\),并且你认为大于等于\(X\)的人都是大人,小于\(X\)的都是小孩。问:找到一个\(X\),使得判断准确人数最多。
思路:在这道题中,\(f(X)\)表示判断准确人数。我们现在考虑什么样的元素会影响哪个区间。对于一个人\(i\),当\(X\)处于哪个区间才能将其分对呢?如果这个人是大人,那么\([0, A_i]\)是可以分对的,\(A_i\)对这个区间贡献\(1\);如果这个人是小孩,那么\([A_i + 1, \infty]\)可以分对的,\(A_i\)对这个区间贡献\(1\)。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 200010;
int n;
char s[N];
map<int, int> mp;
void insert(int l, int r)
{
mp[l] ++, mp[r + 1] --;
}
int main()
{
scanf("%d%s", &n, s + 1);
for(int i = 1; i <= n; i ++) {
int x;
scanf("%d", &x);
if(s[i] == '1') insert(0, x);
else insert(x + 1, 1e9);
}
int ans = 0, t = 0;
for(auto p : mp) {
t += p.second;
ans = max(ans, t);
}
printf("%d\n", ans);
return 0;
}