第九届蓝桥杯省赛CB 8.日志统计【双指针】

这篇博客介绍了如何使用双指针优化算法,将时间复杂度从O(N^2)降低到O(N),以解决在给定时间段内统计帖子点赞数量判断热帖的问题。博主详细阐述了两种解决方案,一种是暴力枚举时间点,另一种是通过对点赞日志排序并使用双指针动态维护区间,从而高效地找出热帖。这两种方法在处理大量数据时效率差异显著,优化后的算法大大提高了处理速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Date:2022.04.02
题意描述:
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。
具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入格式
第一行包含三个整数 N,D,K。
以下 N 行每行一条日志,包含两个整数 ts 和 id。
输出格式
按从小到大的顺序输出热帖 id。
每个 id 占一行。
数据范围
1≤K≤N≤105,
0≤ts,id≤105,
1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

思路①: O ( N 2 ) O(N^2) O(N2)
枚举每一个时间点 t t t,看 [ t , t + d ) [t,t+d) [t,t+d)范围内所有 i d id id有哪些出现了 > = k >=k >=k次的即为热帖。由于每次要枚举所有 i d id id并统计所有 i d id id的数量,所以每次要 m e m s e t memset memset一下 i d id id
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long LL;
LL sum[N],cnt[N],n,k,d,a[N],b[N];
bool st[N];
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>d>>k;LL maxt=0,maxid=0;
    for(int i=1;i<=n;i++) 
    {
        cin>>zan[i].ts>>zan[i].id;
        maxt=max(maxt,zan[i].ts);
    }
    for(int t=0;t<=maxt;t++)
    {
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++)
        {
            LL id=zan[i].id;
            if(zan[i].ts>=t&&zan[i].ts<t+d) cnt[id]++;
            if(cnt[id]>=k) {st[id]=true;continue;}
        }
    }
    LL ans=0;
    for(int i=0;i<N;i++)
        if(st[i]) cout<<i<<'\n';
    return 0;
}

思路②: O ( N ) O(N) O(N)
暴力的瓶颈有两个:
1.枚举的是所有时间,若数据按 t s ts ts排序后是多个1、100000显然枚举所有时间不明智。(当然如果硬说,这个算是软优化)
2.每次都要统计所有 i d id id在这个时间段的出现次数,所以每次要枚举 n n n个数也要 m e m s e t memset memset一下,复杂度自然多一维。
我们将所有点赞按时间为第一关键字排序,这样我们可以用双指针每次来动态维护一个时间在 [ t , t + d ) [t,t+d) [t,t+d)内的区间,但是双指针的 i 、 j i、j ij是所有赞的编号。这样一来,每次后面的 j + + j++ j++,而且对应的 c n t [ z a n [ j ] . i d ] + + cnt[zan[j].id]++ cnt[zan[j].id]++,并且每次将时间的右界变为 z a n [ j ] . t s zan[j].ts zan[j].ts,由此左边 i i i对应的 z a n [ i ] . t s zan[i].ts zan[i].ts如果不满足 z a n [ j ] . t s − z a n [ i ] . t s < d zan[j].ts-zan[i].ts<d zan[j].tszan[i].ts<d那就不要它了,对应的 c n t [ z a n [ i ] . i d ] − − cnt[zan[i].id]-- cnt[zan[i].id],之后 i + + i++ i++
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long LL;
LL cnt[N],n,m,t,k,d;
bool st[N];
struct node
{
    LL ts,id;
    bool operator<(const node&t)const
    {
        if(ts!=t.ts) return ts<t.ts;
        return id<t.id;
    }
}zan[N];
int main()
{
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++)
        cin>>zan[i].ts>>zan[i].id;
    sort(zan+1,zan+1+n);
    LL i,j;
    for(i=1,j=1;i<=n;)
    {
        cnt[zan[i].id]++;
        while(zan[i].ts-zan[j].ts>=d)
        {
            cnt[zan[j].id]--;
            j++;
        }
        if(cnt[zan[i].id]>=k) st[zan[i].id]=true;
        i++;//放最后
    }
    for(int i=0;i<N;i++)
        if(st[i]) cout<<i<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值