蓝桥杯日志统计(两种暴力和双指针)

看了很多篇题解都没有讲暴力法怎么搞,这里讲一下这两点。双指针其他题解已经很清楚了。

暴力法有两种:
一种是先枚举时间区间,然后看这一段时间区间里面的每一个日志的点赞数是否满足要求
一种是先枚举每一个日志,然后枚举对应的时间区间内的赞数

题目描述

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

ts id

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

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

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

给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。
输入格式
  第一行包含三个整数N、D和K。
  以下N行每行一条日志,包含两个整数ts和id。

对于50%的数据,1 <= K <= N <= 1000
  对于100%的数据,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000
输出格式
  按从小到大的顺序输出热帖id。每个id一行。
样例输入
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
样例输出
1
3

双指针是基于暴力的优化,所以先想暴力怎么做。

暴力思路:
在这里插入图片描述
伪代码:

for(时间段)
{
	memset(cnt, 0, sizeof cnt);//每次的时间段都重新计数
	for(所有的日志)
	{
		cnt[id]++;//每一个日志的点赞数量++
		if(cnt[id] >= k) res[id] = true;//如果满足要求就记录 
	}
}

问题来了,虽然是枚举时间段,但是如何枚举

枚举时间段

一开始想的是可以使用i和j控制一段固定长度的区间。像这样

for(int i = 0, j = d; i <= N  && j < N + d; i++, j++)

然后枚举所有的日志的时候这么写

for(int u = 0; u < n; u++)
{
	if(logs[u].first >= i && logs[u].first< j)
		cnt[logs[u].second]++;
	if(cnt[logs[u].second] >= k) 
		res[logs[u].second] = true;
}

逻辑上好像没什么问题,但是时间复杂度太高了。也就是说这种暴力太暴力了。如果题目测试用例给的最大时间为1000,但是只给了10个数。这样写的时间复杂度就是10000.我们看看蓝桥杯第一个测试用例的数据

只给了10个数,但是最小都30000+,时间复杂度一开始就是300000
虽然这也是O(N^2)。
在这里插入图片描述

注:如果一开始就只想拿一半分的话可以这么做,
把N放小一点点,实测N=90000的时候可以拿62分,和通过枚举日志数量的暴力法是一样分数。


枚举日志个数

既然这种枚举时间段的方法不适合ts很大的情况,我们是否可以通过枚举n(日志数量来间接枚举时间段)?

刚刚我们是维护一段长度大小不变的区间来枚举的时间区间。

这次我们可以先枚举每一个日志,然后看一下这一个日志在规定的这一个时间区间内有多少个赞,符合规定就加入答案。(很重要)

for(int i = 0; i < n; i ++)
{
	memset(cnt, 0, sizeof cnt);
	//枚举时间区间内的每一个日志
	for(int j = i; j < n && logs[j].x - logs[i].x < d; j++)//如果长度超过了d就退出循环
	{
		cnt[logs[j].y]++;			
		if(cnt[logs[j].second] >= k)
			res[logs[j].second] = true;
	}
}

下面这一行

for(int j = i; j < n && logs[j].x - logs[i].x < d; j++)

写成这样更能体现在时间区间内枚举每一个日志的思想

for(int j = i; j < n; j++)
{
	if(ogs[j].x - logs[i].x >= d) break;//超出了时间区间长度了
	//操作
}

这种写法也是62分。双指针是基于这种枚举方法来写的。

双指针

还是先枚举日志个数,然后通过控制前后两个指针保证时间区间控制在小于等于d。

其实双指针本质就是维护一段区间,维护方式是可变长度的维护,窗口先变大后变小。前面的指针(第一个循环)维护窗口的右边(变大的方向),后面的指针(第二个循环)维护窗口的左边

一般i是前面那个指针,j是后面那个指针

双指针模板:

for (int i = 0, j = 0; i < n; i ++ )
{
   //第一个循环是窗口变大的逻辑
    while (j < i && check(i, j)) j ++ ;//这里写窗口变小的逻辑

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

伪代码:

for(日志个数)
{
	//窗口变大的逻辑,把这个赞加进来
	cnt[id[i]]++;
	while(j < n && logs[j].y - logs[j].x < d) cnt[id[j]]--;//窗口变小的逻辑,把这个赞减掉
}

ac代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;
const int N = 100010;
int cnt[N];
int res[N];
int n, d, k;
typedef pair<int, int> pii;
pii logs[N];

int main()
{
    cin >> n >> d >> k;
    for(int i = 0; i < n; i++) scanf("%d%d", &logs[i].first, &logs[i].second);
    sort(logs, logs + n);
    for(int i = 0, j = 0; i < n; i++)
    {
        cnt[logs[i].second]++;
        while(j <= i && logs[i].first - logs[j].first >= d)
        {
            cnt[logs[j].second]--;
            j++;
        }
        if(cnt[logs[i].second] >= k) res[logs[i].second] = 1;
    }
    
    for(int i = 1; i <= 100000; i++)
        if(res[i] == 1)
            printf("%d\n", i);
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值