蓝桥杯第十一讲--双指针【例/习题】

举报
辰chen 发表于 2022/06/14 23:47:18 2022/06/14
【摘要】 文章目录 前言日志统计题目要求思路分析代码 完全二叉树的权值题目要求思路分析代码 递增三元组题目要求思路分析代码 前言 蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事 ✨本...

前言

蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事
✨本博客讲解 蓝桥杯C/C++ 备赛所涉及算法知识,此博客为第十一讲:双指针【例/习题】

本篇博客所包含习题有:
👊日志统计
👊完全二叉树的权值
👊递增三元组

博客内容以题代讲,通过讲解题目的做法来帮助读者快速理解算法内容,需要注意:学习算法不能光过脑,更要实践,请读者务必自己敲写一遍本博客相关代码!!!


日志统计

来源: 第九届蓝桥杯省赛C++B组,第九届蓝桥杯省赛JAVAB组

题目要求

题目描述:

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N N N 行。

其中每一行的格式是:

ts id  

  
 
  • 1

表示在 t s ts ts 时刻编号 i d id id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D D D 的时间段内收到不少于 K K K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T T T 满足该帖在 [ T , T + D ) [T,T+D) [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K K K 个赞,该帖就曾是”热帖”。

给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式:

第一行包含三个整数 N , D , K N,D,K N,D,K

以下 N N N 行每行一条日志,包含两个整数 t s ts ts i d id id

输出格式:

按从小到大的顺序输出热帖 i d id id

每个 i d id id 占一行。

数据范围:

1 ≤ K ≤ N ≤ 1 0 5 , 1≤K≤N≤10^5 , 1KN105,
0 ≤ t s , i d ≤ 1 0 5 , 0≤ts,id≤10^5, 0ts,id105,
1 ≤ D ≤ 10000 1≤D≤10000 1D10000

输入样例:

7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3

输出样例:

1
3

思路分析

l o g s logs logs 数组存储的是日志信息, c n t [ i ] cnt[i] cnt[i] 表示的是 i d id id i i i 的帖子在一段时间内的获赞量, s t [ i ] st[i] st[i] 用来判断 i d id id i i i 的帖子是否曾经是个热帖,如果是,那么有:st[i] = true;,否则有:st[i] = false;,本题的双指针在于用 i i i j j j 表示一段时间,每次取出新增的 i d id id,然后让这个 i d id idcnt[id] ++;,然后判断此时的时间跨度是否大于了规定的 D D D,如果大于,那么就让 j j j 向后挪动一格,因为挪动后原先的 j j j 就不在段时间的范围内,故有:cnt[logs[j].y] --;,挪动后判断新增的这个 i d id id 是否有:cnt[id] >= k,若有,则证明这个 i d id id 是一个热帖:st[id] = true;,最后遍历所有的 i d id id,把符合的 i d id id 输出即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

PII logs[N];
int cnt[N];
bool st[N];

int main()
{
    int n, d, k;
    scanf("%d%d%d", &n, &d, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d%d", &logs[i].x, &logs[i].y);
    
    sort(logs, logs + n);
    
    for (int i = 0, j = 0; i < n; i ++ ) 
    {
        int id = logs[i].y;
        cnt[id] ++;
        
        while (logs[i].x - logs[j].x >= d)
        {
            cnt[logs[j].y] --;
            j ++;
        }
        
        if (cnt[id] >= k) st[id] = true;
    }
    
    for (int i = 0; i <= 100000; i ++ ) 
        if (st[i]) printf("%d\n", i);
        
    return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

完全二叉树的权值

来源: 第十届蓝桥杯省赛C++A/B组,第十届蓝桥杯省赛JAVAA组

题目要求

题目描述:

给定一棵包含 N N N 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是 A 1 , A 2 , ⋅ ⋅ ⋅ A N A_1,A_2,⋅⋅⋅A_N A1,A2,AN,如下图所示:
在这里插入图片描述
现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?

如果有多个深度的权值和同为最大,请你输出其中最小的深度。

注:根的深度是 1 1 1

输入格式:

第一行包含一个整数 N N N

第二行包含 N N N 个整数 A 1 , A 2 , ⋅ ⋅ ⋅ A N A_1,A_2,⋅⋅⋅A_N A1,A2,AN

输出格式:

输出一个整数代表答案。

数据范围:

1 ≤ N ≤ 1 0 5 , 1≤N≤10^5, 1N105,
− 1 0 5 ≤ A i ≤ 1 0 5 −10^5≤A_i≤10^5 105Ai105

输入样例:

7
1 6 5 4 3 2 1

输出样例:

2

思路分析

我们一层一层的去遍历,对于构造一个完全二叉树,我们可以按照如下进行构造:
在这里插入图片描述
即数组从 1 1 1 开始进行,那么对于一个结点 i i i i × 2 i\times2 i×2 就是左儿子, i × 2 + 1 i \times2+1 i×2+1 就是右儿子,不难看出,假设此时的深度为 d d d,那么这层的最左边的儿子的编号 i = 2 d − 1 i=2^{d-1} i=2d1,该层的节点编号 j ∈ [ i , i + 2 d − 1 ) j ∈[i, i+2^{d-1}) j[i,i+2d1),故我们可以一层一层的遍历,每次都计算每一层的点数之和,用 M a x Max Max 记录点数和的最大值,用 d e p t h depth depth 记录点数最大的层数。本题代码虽然有两层循环,但是其实每一个节点只会遍历一次,故本题代码的时间复杂度为 O ( n ) O(n) O(n)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int a[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    LL Max = -1e18;
    int depth = 0;
    for (int d = 1, i = 1; i <= n; i *= 2, d ++ )    //遍历每一层
    {
        LL temp = 0;
        for (int j = i; j < i + (1 << d - 1) && j <= n; j ++ )
            temp += a[j];
            
        if (temp > Max)
        {
            Max = temp;
            depth = d;
        }
    }
    
    printf("%d\n", depth);
    
    return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

递增三元组

来源: 第九届蓝桥杯省赛C++B组,第九届蓝桥杯省赛JAVAB组

题目要求

题目描述:

给定三个整数数组

A = [ A 1 , A 2 , … A N ] , A=[A_1,A_2,…A_N], A=[A1,A2,AN],
B = [ B 1 , B 2 , … B N ] , B=[B_1,B_2,…B_N], B=[B1,B2,BN],
C = [ C 1 , C 2 , … C N ] , C=[C_1,C_2,…C_N], C=[C1,C2,CN],

请你统计有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k) 满足:

1 ≤ i , j , k ≤ N 1≤i,j,k≤N 1i,j,kN
A i < B j < C k A_i<B_j<C_k Ai<Bj<Ck

输入格式:

第一行包含一个整数 N N N

第二行包含 N N N 个整数 A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,AN

第三行包含 N N N 个整数 B 1 , B 2 , … B N B_1,B_2,…B_N B1,B2,BN

第四行包含 N N N 个整数 C 1 , C 2 , … C N C_1,C_2,…C_N C1,C2,CN

输出格式:

一个整数表示答案。

数据范围:

1 ≤ N ≤ 1 0 5 , 1≤N≤10^5, 1N105,
0 ≤ A i , B i , C i ≤ 1 0 5 0≤A_i,B_i,C_i≤10^5 0Ai,Bi,Ci105

输入样例:

3
1 1 1
2 2 2
3 3 3

输出样例:

27

思路分析

本题有三种解法,本博客只讲解双指针的解法,对于前缀和、二分的解法见博客:蓝桥杯第八讲–枚举与模拟【例题】

双指针的解法同二分类似,先对三个数组分别进行排序,然后遍历 b b b数组,其中 i , j , k i,j,k i,j,k 分别是 a , b , c a,b,c a,b,c数组的指针,对于每一个 b [ j ] b[j] b[j],对数组 a a a 的操作为:while (i < n && a[i] < b[j]) i ++;,特别注意,执行完这句时候, i i i 指针停留的位置其实是第一个大于等于 b [ j ] b[j] b[j] 的位置,即从 0 0 0 ~ i − 1 i-1 i1 是符合题意的,对数组 c c c 的操作为:while (k < n && c[k] <= b[j]) k ++;,执行完这行代码, k k k 停留的位置即第一个大于 b [ j ] b[j] b[j] 的位置,即从 k k k ~ n − 1 n-1 n1 是符合题意的,故我们用 res += i * (n - 1 - k + 1);。接着分析本题是否会爆 i n t int int:如果数组 A , B , C A,B,C A,B,C 正好是统一单调递增的,那么这个时候三元组的数量约为: N × N = 1 0 10 N \times N=10^{10} N×N=1010,即本题会爆 i n t int int,故我们的 r e s res res l o n g long long l o n g long long 去存。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int a[N], b[N], c[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    for (int i = 0; i < n; i ++ ) scanf("%d", &b[i]);
    for (int i = 0; i < n; i ++ ) scanf("%d", &c[i]);
    
    sort(a, a + n);
    sort(b, b + n);
    sort(c, c + n);
    
    LL res = 0;
    for (int i = 0, j = 0, k = 0; j < n; j ++ )
    {
        while (i < n && a[i] < b[j]) i ++;
        while (k < n && c[k] <= b[j]) k ++;
        res += (LL)i * (n - 1 - k + 1);
    }
    
    printf("%lld\n", res);
    
    return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

文章来源: chen-ac.blog.csdn.net,作者:辰chen,版权归原作者所有,如需转载,请联系作者。

原文链接:chen-ac.blog.csdn.net/article/details/122782117

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。