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;
}
posted @ 2024-03-05 18:17  Cocoicobird  阅读(25)  评论(0编辑  收藏  举报