一类差分题的思考方式及经典例题

问题描述

这类题目一般都会给定一个整数序列\(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;
}

题意:有一群大人和小孩,每个人都有一个高度\(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;
}
posted @ 2022-06-27 15:36  pbc的成长之路  阅读(58)  评论(0编辑  收藏  举报