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


    问题描述

    这类题目一般都会给定一个整数序列\(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;
    }
    
  • 相关阅读:
    Bugku-CTF之各种绕过
    算法竞赛入门经典 第四章 学习笔记 2
    时钟周期,CPU周期,指令周期,CPU时间片
    big-endian和little-endian
    android px,dp,sp大小转换工具
    SharedPreferences漏洞, 无法避免,所以不要在里面存储敏感信息
    java正则表达式入门
    adb命令
    android largeheap 的设定
    关于java中接口定义常量和类定义常量的区别
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16416301.html
Copyright © 2020-2023  润新知