2024AcWing蓝桥杯集训·每日一题-双指针
1.[AcWing3745.牛的学术圈 I]
题目描述
由于对计算机科学的热爱,以及有朝一日成为 「Bessie 博士」的诱惑,奶牛 Bessie 开始攻读计算机科学博士学位。
经过一段时间的学术研究,她已经发表了 \(N\) 篇论文,并且她的第 \(i\) 篇论文得到了来自其他研究文献的 \(c_i\) 次引用。
Bessie 听说学术成就可以用 \(h\) 指数来衡量。
\(h\) 指数等于使得研究员有至少 \(h\) 篇引用次数不少于 \(h\) 的论文的最大整数 \(h\)。
例如,如果一名研究员有 \(4\) 篇论文,引用次数分别为 \((1,100,2,3)\),则 \(h\) 指数为 \(2\),然而若引用次数为 \((1,100,3,3)\) 则 \(h\) 指数将会是 \(3\)。
为了提升她的 \(h\) 指数,Bessie 计划写一篇综述,并引用一些她曾经写过的论文。
由于页数限制,她至多可以在这篇综述中引用 \(L\) 篇论文,并且她只能引用每篇她的论文至多一次。
请帮助 Bessie 求出在写完这篇综述后她可以达到的最大 \(h\) 指数。
注意 Bessie 的导师可能会告知她纯粹为了提升 \(h\) 指数而写综述存在违反学术道德的嫌疑;我们不建议其他学者模仿 Bessie 的行为。
输入格式
输入的第一行包含 \(N\) 和 \(L\)。
第二行包含 \(N\) 个空格分隔的整数 \(c_1,…,c_N\)。
输出格式
输出写完综述后 Bessie 可以达到的最大 \(h\) 指数。
数据范围
\(1≤N≤10^5,\)
\(0≤c_i≤10^5,\)
\(0≤L≤10^5\)
输入样例1
4 0
1 100 2 3
输出样例1
2
样例1解释
Bessie 不能引用任何她曾经写过的论文。上文中提到,\((1,100,2,3)\) 的 \(h\) 指数为 \(2\)。
输入样例2
4 1
1 100 2 3
输出样例2
3
样例2解释
如果 Bessie 引用她的第三篇论文,引用数会变为 \((1,100,3,3)\)。上文中提到,这一引用数的 \(h\) 指数为 \(3\)。
解题思路
降序排序,找到最大的 \(i\) 使得 \(c_i≥i\),此时不额外引用(每个文章最多被引用一次)的最大的 \(h\) 即为 \(i\),每个数最多只能加 \(1\),所以 \(h\) 指数最大只能是 \(h+1\)。因此需要取部分数进行加一操作,首先是 \(c_{h+1}\),如果 \(c_{h+1}<h\),最多等于 \(h\),则不会出现 \(c_{h+1}=h+1\) 的情况,其后面的数也是一样,因此此情况最多为 \(h\)。否则 \(c_{h+1}=h\)(根据前面 \(h\) 的初步确定 \(c_{h+1}<=h\) 和 \(c_{h+1}<h\) 不成立),那么由此往前找 \(c_i=h\) 的进行加一,若个数小于等于 \(L\) 则指数增加为 \(h+1\),否则仍为 \(h\)。
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, L;
int c[N];
int main() {
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
sort(c + 1, c + n + 1);
reverse(c + 1, c + n + 1);
int h = 0;
for (int i = 1; i <= n; i++) {
if (c[i] >= i) h = i;
else break;
}
if (c[h + 1] < h) {
printf("%d\n", h);
return 0;
}
int cnt = 0;
for (int i = 1; i <= h + 1; i++)
if (c[i] == h)
cnt++;
if (cnt <= L)
printf("%d\n", h + 1);
else
printf("%d\n", h);
/* 双指针做法
// 关于 h
// 1. 最小值大于等于 h-1
// 2. h-1 的个数小于等于 L
int res = 0;
for (int i = 1, j = n; i <= n; i++) {
while (j && c[j] < i) j--;
if (c[i] >= i - 1 && i - j <= L)
res = i;
}
cout << res << endl;
*/
return 0;
}
2.[AcWing1238.日志统计]
题目描述
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 \(N\) 行。
其中每一行的格式是:
ts id
表示在 \(ts\) 时刻编号 \(id\) 的帖子收到一个“赞”。现在小明想统计有哪些帖子曾经是“热帖”。如果一个帖子曾在任意一个长度为 \(D\) 的时间段内收到不少于 \(K\) 个赞,小明就认为这个帖子曾是“热帖”。
具体来说,如果存在某个时刻 \(T\) 满足该帖在 \([T,T+D)\) 这段时间内(注意是左闭右开区间)收到不少于 \(K\) 个赞,该帖就曾是“热帖”。
给定日志,请你帮助小明统计出所有曾是“热帖”的帖子编号。
输入格式
第一行包含三个整数 \(N,D,K\)。
以下 \(N\) 行每行一条日志,包含两个整数 \(ts\) 和 \(id\)。
输出格式
按从小到大的顺序输出热帖 \(id\)。
每个 \(id\) 占一行。
数据范围
\(1≤K≤N≤10^5,\)
\(0≤ts,id≤10^5,\)
\(1≤D≤10000\)
输入样例
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例
1
3
解题思路
双指针。使用数对数组按照时间排序,在时间区间内维护 \(cnt\) 数组统计点赞次数。
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef pair<int, int> PII;
#define ts first
#define id second
int n, D, K;
PII q[N];
bool st[N];
int cnt[N];
int main() {
scanf("%d%d%d", &n, &D, &K);
for (int i = 1; i <= n; i++)
scanf("%d%d", &q[i].ts, &q[i].id);
sort(q + 1, q + n + 1);
for (int i = 1, j = 1; i <= n; i++) { // i 是右指针 j 是左指针
cnt[q[i].id]++; // 点赞次数+1
while (q[i].ts - q[j].ts >= D) { // 保证时间区间
cnt[q[j].id]--;
j++;
}
if (cnt[q[i].id] >= K) st[q[i].id] = true;
}
for (int i = 0; i < N; i++)
if (st[i])
printf("%d\n", i);
return 0;
}
本文来自博客园,作者:Cocoicobird,转载请注明原文链接:https://www.cnblogs.com/Cocoicobird/p/18054621