问题
题目描述
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。
具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入格式
第一行包含三个整数 N,D,K。
以下 N 行每行一条日志,包含两个整数 ts 和 id。
输出格式
按从小到大的顺序输出热帖 id。
每个 id 占一行。
数据范围
1≤K≤N≤10E5,
0≤ts,id≤10E5,
1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3
第九届蓝桥杯省赛C++B组,第九届蓝桥杯省赛JAVAB组
解析
尺取法
顾名思义,就是像一把尺子一样,在变换尺子的长度中,一段段的截出满足条件的序列,然后进行相应的操作。
简介
尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。
尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以说尺取法是一种高效的枚举区间的方法,是一种技巧,一般用于求取有一定限制的区间个数或最短的区间等等。
当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案,所以要先判断是否可以使用尺取法再进行计算。
适用条件
尺取法一般适用于在连续区间内求解问题,例如连续之和之类的。通过不断地变换尺子的长度,来选择最优的满足条件的序列。
解题步骤
- 选取相应的左右端点并初始化
- 推进右端点直到满足条件
- 判断当前的序列是否满足条件,不满足则跳出
- 更新相应的答案
- 左端点推进一步,缩小尺子范围
- 重复2~6步骤,直至跳出循环
代码
import java.util.*;
/**
* 日志类
*/
class Log implements Comparable<Log>{
int ts;//时刻
int id;//编号
public Log(int ts,int id) {
this.ts =ts;
this.id=id;
}
public int compareTo(Log o) {
return Integer.compare(ts, o.ts);//根据初始时间大小排序
}
}
/**
* 日志统计:代码实现
*
* 查找一段时间区间获赞最多的id
*/
public class logStatistics {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt(), //行数
d=scanner.nextInt(), //时间区间
k=scanner.nextInt(); // 满足赞数
int N=100010; // 极值
Log[] logs=new Log[N];//日志列表
int[] cnt=new int[N];//记录当前时间段各id的点赞数
boolean[] isOK=new boolean[N];//记录满足点赞要求的id
for(int i=0;i<n;i++) {
int ts=scanner.nextInt(), //ts时刻
id=scanner.nextInt(); //编号id
logs[i]=new Log(ts, id);
}
Arrays.sort(logs,0,n);//让日志根据时间从小到大排列
for(int i=0,j=0;i<n;i++) {
int id=logs[i].id;
cnt[id]++;//该条日志的id点赞加一
while(logs[i].ts -logs[j].ts >=d) {//i走在前面,j走在后面,如果第i条日志的时间与第j条日志的时间差大于d,则j往前走一步
cnt[logs[j].id]--;//原先的日志j就不包含在时间段内,则该id点赞数减一
j++;//往前走一步
}
if(cnt[id]>=k) {
isOK[id]=true;//若j~i时间段,id获赞总数满足要求
}
}
//按从小到大的顺序输出热帖 id
for(int i=1;i<N;i++) {//id从小到大遍历
if(isOK[i])
System.out.println(i);
}
}
}