蓝桥杯精选赛题系列——日志统计——尺取法(2018年省赛)

首先在看例题前,给大家介绍一种常用的算法——尺取法。

尺取法概念:

尺取法也被称为双指针、two pointers,是算法竞赛中一个常用的优化技巧,是用来解决序列的区间问题。操作简单、容易编程。简单来说,可以把两重循环转化为一重循环,从而把时间复杂度从 O(n2)提高到 O(n)。

举个栗子:

首先看一个用 i 和 j执行的两重循环:

for(int i = 0; i < n; i++)           //i从头扫到尾
    for(int j = n-1; j >= 0; j--){
    
       //j从尾扫到头
        ......
    }

其中 i从 0​​​ 循环到 n−1​​​,j 反过来从 n−1​​ 循环到 0​​ 。这两重循环的时间复杂度是 O(n2) 的。
尺取法可以在一个循环中一起处理 i,j,代码如下,这样复杂度就从 O(n2)变成了 O(n)。

for (int i = 0, j = n - 1; i < j; i++, j--) {
    
    
    ......
}

还有另一种while()写法:

//用while实现:
int i = 0, j = n - 1;
while (i < j) {
    
          //i和j在中间相遇。这样做还能防止i、j越界
        ......       //满足题意的操作
        i++;         //i从头扫到尾
        j--;         //j从尾扫到头
}

以上只是一种反向扫描,患有另一种同向扫描。
蓝桥杯大佬做出了总结,看看下面就会明白了:

把循环指针 i 、j 称为 扫描指针,在尺取法中,这两个指针 i、j,有两种扫描方向:
反向扫描 i、j 方向相反,i 从头到尾,j 从尾到头,在中间相会。也可以把反向扫描的 i、j 指针称为左右指针
同向扫描 i、j 方向相同,都从头到尾,但是速度不一样,比如可以让 j 跑在 i 前面。也可以把同向扫描的 i、j 指针称为快慢指针,此时由于 i 和 j 速度不同,i 和 j 之间在序列上产生了一个大小可变的滑动窗口,这是尺取法的优势,有灵活的应用。
注意,用尺取法的最关键之处在于,两个指针 i、j 在总体上只能有一个循环,例如:i 循环一遍,对应的 j 只能跟随 i 循环一遍。这样才能实现计算复杂度从 O(n2) 到 O(n)​ 的优化。

接下来是一个省赛的真题,用python暴力法,和c++的尺取法复杂度差不多:

题目描述

小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有 N 行。其中每一行的格式是:

ts id

表示在 ts 时刻编号 id 的帖子收到一个"赞"。

现在小明想统计有哪些帖子曾经是"热帖"。如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K个赞,小明就认为这个帖子曾是"热帖"。

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

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

输入描述

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

以下 N 行每行一条日志,包含两个整数 ts 和 id。

其中,1<=K<=N<=105,0<=ts<=105,0<=id<=105

输出描述

按从小到大的顺序输出热帖 id。每个 id 一行。

样例输入

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

样例输出

1
3

详细解答

虽然我们上面介绍了尺取法,这个可以作为了解,本题使用python的暴力法和c++的尺取法复杂度差不多,所以在这里我首先采用python暴力法(因为代码好写,嘿嘿):

from bisect import bisect_left
maxn=int(1e5+50)
n,d,k=map(int,input().split())
m=[[] for _ in range(maxn)]
post=set()
for _ in range(n):
    ts,idd=map(int,input().split())
    post.add(idd)
    m[idd].append(ts)  #读每个帖子的赞的时间
post = sorted(post)    #对帖子id排序
for idd in post:       #检查每个帖子
    m[idd]=sorted(m[idd])  #把某个帖子的ts排序
    for i in range(len(m[idd])):   #暴力统计这个帖子是不是热帖
        td = m[idd][i]+d
        if(bisect_left(m[idd],td)-i >= k):
            print(idd)
            break

猜你喜欢

转载自blog.csdn.net/m0_51951121/article/details/122648992