文章目录
前言
蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事
✨本博客讲解 蓝桥杯C/C++ 备赛所涉及算法知识,此博客为第十一讲:双指针【例/习题】
本篇博客所包含习题有:
👊日志统计
👊完全二叉树的权值
👊递增三元组
博客内容以题代讲,通过讲解题目的做法来帮助读者快速理解算法内容,需要注意:学习算法不能光过脑,更要实践,请读者务必自己敲写一遍本博客相关代码!!!
日志统计
题目要求
题目描述:
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N N N 行。
其中每一行的格式是:
ts id
表示在 t s ts ts 时刻编号 i d id id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D D D 的时间段内收到不少于 K K K 个赞,小明就认为这个帖子曾是”热帖”。
具体来说,如果存在某个时刻 T T T 满足该帖在 [ T , T + D ) [T,T+D) [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K K K 个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入格式:
第一行包含三个整数 N , D , K N,D,K N,D,K。
以下 N N N 行每行一条日志,包含两个整数 t s ts ts 和 i d id id。
输出格式:
按从小到大的顺序输出热帖 i d id id。
每个 i d id id 占一行。
数据范围:
1
≤
K
≤
N
≤
1
0
5
,
1≤K≤N≤10^5 ,
1≤K≤N≤105,
0
≤
t
s
,
i
d
≤
1
0
5
,
0≤ts,id≤10^5,
0≤ts,id≤105,
1
≤
D
≤
10000
1≤D≤10000
1≤D≤10000
输入样例:
输出样例:
思路分析
l
o
g
s
logs
logs 数组存储的是日志信息,
c
n
t
[
i
]
cnt[i]
cnt[i] 表示的是
i
d
id
id 为
i
i
i 的帖子在一段时间内的获赞量,
s
t
[
i
]
st[i]
st[i] 用来判断
i
d
id
id 为
i
i
i 的帖子是否曾经是个热帖,如果是,那么有:st[i] = true;
,否则有:st[i] = false;
,本题的双指针在于用
i
i
i 和
j
j
j 表示一段时间,每次取出新增的
i
d
id
id,然后让这个
i
d
id
id 的 cnt[id] ++;
,然后判断此时的时间跨度是否大于了规定的
D
D
D,如果大于,那么就让
j
j
j 向后挪动一格,因为挪动后原先的
j
j
j 就不在段时间的范围内,故有:cnt[logs[j].y] --;
,挪动后判断新增的这个
i
d
id
id 是否有:cnt[id] >= k
,若有,则证明这个
i
d
id
id 是一个热帖:st[id] = true;
,最后遍历所有的
i
d
id
id,把符合的
i
d
id
id 输出即可。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 100010;
PII logs[N];
int cnt[N];
bool st[N];
int main()
{
int n, d, k;
scanf("%d%d%d", &n, &d, &k);
for (int i = 0; i < n; i ++ ) scanf("%d%d", &logs[i].x, &logs[i].y);
sort(logs, logs + n);
for (int i = 0, j = 0; i < n; i ++ )
{
int id = logs[i].y;
cnt[id] ++;
while (logs[i].x - logs[j].x >= d)
{
cnt[logs[j].y] --;
j ++;
}
if (cnt[id] >= k) st[id] = true;
}
for (int i = 0; i <= 100000; i ++ )
if (st[i]) printf("%d\n", i);
return 0;
}
完全二叉树的权值
题目要求
题目描述:
给定一棵包含
N
N
N 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是
A
1
,
A
2
,
⋅
⋅
⋅
A
N
A_1,A_2,⋅⋅⋅A_N
A1,A2,⋅⋅⋅AN,如下图所示:
现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?
如果有多个深度的权值和同为最大,请你输出其中最小的深度。
注:根的深度是 1 1 1。
输入格式:
第一行包含一个整数 N N N。
第二行包含 N N N 个整数 A 1 , A 2 , ⋅ ⋅ ⋅ A N A_1,A_2,⋅⋅⋅A_N A1,A2,⋅⋅⋅AN。
输出格式:
输出一个整数代表答案。
数据范围:
1
≤
N
≤
1
0
5
,
1≤N≤10^5,
1≤N≤105,
−
1
0
5
≤
A
i
≤
1
0
5
−10^5≤A_i≤10^5
−105≤Ai≤105
输入样例:
输出样例:
思路分析
我们一层一层的去遍历,对于构造一个完全二叉树,我们可以按照如下进行构造:
即数组从
1
1
1 开始进行,那么对于一个结点
i
i
i,
i
×
2
i\times2
i×2 就是左儿子,
i
×
2
+
1
i \times2+1
i×2+1 就是右儿子,不难看出,假设此时的深度为
d
d
d,那么这层的最左边的儿子的编号
i
=
2
d
−
1
i=2^{d-1}
i=2d−1,该层的节点编号
j
∈
[
i
,
i
+
2
d
−
1
)
j ∈[i, i+2^{d-1})
j∈[i,i+2d−1),故我们可以一层一层的遍历,每次都计算每一层的点数之和,用
M
a
x
Max
Max 记录点数和的最大值,用
d
e
p
t
h
depth
depth 记录点数最大的层数。本题代码虽然有两层循环,但是其实每一个节点只会遍历一次,故本题代码的时间复杂度为
O
(
n
)
O(n)
O(n)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
LL Max = -1e18;
int depth = 0;
for (int d = 1, i = 1; i <= n; i *= 2, d ++ ) //遍历每一层
{
LL temp = 0;
for (int j = i; j < i + (1 << d - 1) && j <= n; j ++ )
temp += a[j];
if (temp > Max)
{
Max = temp;
depth = d;
}
}
printf("%d\n", depth);
return 0;
}
递增三元组
题目要求
题目描述:
给定三个整数数组
A
=
[
A
1
,
A
2
,
…
A
N
]
,
A=[A_1,A_2,…A_N],
A=[A1,A2,…AN],
B
=
[
B
1
,
B
2
,
…
B
N
]
,
B=[B_1,B_2,…B_N],
B=[B1,B2,…BN],
C
=
[
C
1
,
C
2
,
…
C
N
]
,
C=[C_1,C_2,…C_N],
C=[C1,C2,…CN],
请你统计有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k) 满足:
1
≤
i
,
j
,
k
≤
N
1≤i,j,k≤N
1≤i,j,k≤N
A
i
<
B
j
<
C
k
A_i<B_j<C_k
Ai<Bj<Ck
输入格式:
第一行包含一个整数 N N N。
第二行包含 N N N 个整数 A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,…AN。
第三行包含 N N N 个整数 B 1 , B 2 , … B N B_1,B_2,…B_N B1,B2,…BN。
第四行包含 N N N 个整数 C 1 , C 2 , … C N C_1,C_2,…C_N C1,C2,…CN。
输出格式:
一个整数表示答案。
数据范围:
1
≤
N
≤
1
0
5
,
1≤N≤10^5,
1≤N≤105,
0
≤
A
i
,
B
i
,
C
i
≤
1
0
5
0≤A_i,B_i,C_i≤10^5
0≤Ai,Bi,Ci≤105
输入样例:
输出样例:
思路分析
本题有三种解法,本博客只讲解双指针的解法,对于前缀和、二分的解法见博客:蓝桥杯第八讲–枚举与模拟【例题】
双指针的解法同二分类似,先对三个数组分别进行排序,然后遍历
b
b
b数组,其中
i
,
j
,
k
i,j,k
i,j,k 分别是
a
,
b
,
c
a,b,c
a,b,c数组的指针,对于每一个
b
[
j
]
b[j]
b[j],对数组
a
a
a 的操作为:while (i < n && a[i] < b[j]) i ++;
,特别注意,执行完这句时候,
i
i
i 指针停留的位置其实是第一个大于等于
b
[
j
]
b[j]
b[j] 的位置,即从
0
0
0 ~
i
−
1
i-1
i−1 是符合题意的,对数组
c
c
c 的操作为:while (k < n && c[k] <= b[j]) k ++;
,执行完这行代码,
k
k
k 停留的位置即第一个大于
b
[
j
]
b[j]
b[j] 的位置,即从
k
k
k ~
n
−
1
n-1
n−1 是符合题意的,故我们用 res += i * (n - 1 - k + 1);
。接着分析本题是否会爆
i
n
t
int
int:如果数组
A
,
B
,
C
A,B,C
A,B,C 正好是统一单调递增的,那么这个时候三元组的数量约为:
N
×
N
=
1
0
10
N \times N=10^{10}
N×N=1010,即本题会爆
i
n
t
int
int,故我们的
r
e
s
res
res 用
l
o
n
g
long
long
l
o
n
g
long
long 去存。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int a[N], b[N], c[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
for (int i = 0; i < n; i ++ ) scanf("%d", &b[i]);
for (int i = 0; i < n; i ++ ) scanf("%d", &c[i]);
sort(a, a + n);
sort(b, b + n);
sort(c, c + n);
LL res = 0;
for (int i = 0, j = 0, k = 0; j < n; j ++ )
{
while (i < n && a[i] < b[j]) i ++;
while (k < n && c[k] <= b[j]) k ++;
res += (LL)i * (n - 1 - k + 1);
}
printf("%lld\n", res);
return 0;
}