A. 区间选数
给定两个整数区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 和 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]。
请你找到两个整数 a a a 和 b b b,要求:
l
1
≤
a
≤
r
1
l_1\leq a\leq r_1
l1≤a≤r1
l
2
≤
b
≤
r
2
l_2\leq b\leq r_2
l2≤b≤r2
a
≠
b
a\neq b
a=b
输入格式
第一行包含整数 T T T,表示共有 T T T 组测试数据。
每组数据占一行,包含四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_ 2 l1,r1,l2,r2。
输出格式
每组数据输出一行结果,包含两个整数 a a a 和 b b b。
如果答案不唯一,输出任意合理方案均可。
保证一定有解。
数据范围
前三个测试点满足
1
≤
T
≤
10
1\leq T\leq10
1≤T≤10。
所有测试点满足
1
≤
T
≤
500
1\leq T\leq 500
1≤T≤500,
1
≤
l
1
<
r
1
≤
1
0
9
1\leq l_1 < r_1\leq 10^9
1≤l1<r1≤109,
1
≤
l
2
<
r
2
≤
1
0
9
1\leq l_2 < r_2\leq 10^9
1≤l2<r2≤109。
输入样例:
5
1 2 1 2
2 6 3 4
2 4 1 3
1 2 1 3
1 4 5 8
输出样例:
2 1
3 4
3 2
1 2
3 7
题目分析:
手速题,随便取数,只要二者不相等即可。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
int T;
cin >> T;
int l1, l2, r1, r2;
while (T -- )
{
bool flag = false;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
for (int i = l1; i <= r1; i ++ )
{
for (int j = l2; j <= r2; j ++ )
if (i != j)
{
printf("%d %d\n", i, j);
flag = true;
break;
}
if (flag) break;
}
}
return 0;
}
B.食堂排队
某班有 n n n 个学生,编号 1 ∼ n 1\sim n 1∼n。
中午下课后,学生们陆续赶到食堂吃饭。
食堂只有一个打饭窗口,所以学生们要排队打饭。
第 i i i 个学生在第 l i l_i li 分钟开始排队(当然是排在队尾)。
如果同一分钟有多个学生同时开始排队,则编号较小的学生排在编号较大的学生之前。
当一名学生排在队伍的最前端时,即轮到该名学生打饭。
每个学生打饭需要花费一分钟时间。
打到饭的学生,立即离开队伍,没打到饭的学生,在队伍里继续等待。
每个学生的耐心都是有限的。
对于第 i i i 个学生,如果在第 r i r_i ri 分钟时,他仍在排队,且未轮到他打饭(仍有人排在他之前),那么他就会直接离开,放弃打饭。
对于每个学生,请你确定他是在第几分钟开始打饭(即排到队首)的?
输入格式
第一行包含整数 T T T,表示共有 T T T 组测试数据。
每组数据第一行包含整数 n n n。
接下来 n n n 行,每行包含两个整数 l i , r i l_i,r_i li,ri,表示第 i i i个学生到达队伍的时间以及忍耐的极限时间。
保证同一组数据的 l i l_i li 是非递减的。
输出格式
每组数据输出一行结果,包含 n n n 个整数,其中第 i i i 个整数表示第 i i i 个学生开始打饭的时间,如果这个学生直接离开,放弃打饭,则输出 0 0 0。
数据范围
前三个测试点满足
1
≤
n
≤
3
1\leq n\leq 3
1≤n≤3。
所有测试点满足
1
≤
T
≤
1000
1\leq T\leq 1000
1≤T≤1000,
1
≤
n
≤
1000
1\leq n\leq 1000
1≤n≤1000,
1
≤
l
i
≤
r
i
≤
5000
1\leq l_i\leq r_i\leq 5000
1≤li≤ri≤5000。
同一测试点内所有
n
n
n 的和不超过
1000
1000
1000。
输入样例:
2
2
1 3
1 4
3
1 5
1 1
2 3
输出样例:
1 2
1 0 2
题目分析:
由于题意已经明确,所有同学的插入队列时间都是递增的,因此我们只要按序模拟即可。
令最后一个人打饭结束的时间为last
,我们需要判断后一个人的插入队列的时间l
。如果l < last
,则后一个人需要等待,若直到极限忍耐时间r
都还没有打到饭,那么这个人直接溜了;如果l >= last
,那么后一个人可以直接打饭,并且开始打饭的时间更新为开始打饭时间 + 1 即可。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
int T;
cin >> T;
int n;
while (T -- )
{
scanf("%d", &n);
int last = 0;
while (n -- )
{
int l, r;
scanf("%d%d", &l, &r);
// 如果开始打饭的时间大于极限忍耐时间,就走人
if (max(last, l) > r) printf("0 ");
else
{
printf("%d ", max(last, l));
last = max(last, l) + 1;
}
}
puts("");
}
return 0;
}
C.寻找字符串
给定一个由小写字母构成的字符串 s s s。
请你找到一个满足如下所有要求的字符串 t t t:
字符串
t
t
t 是字符串
s
s
s 的前缀。
字符串
t
t
t 是字符串
s
s
s 的后缀。
字符串
t
t
t 在字符串
s
s
s 的中间出现过。也就是作为一个既非前缀也非后缀的子串出现过。
字符串
t
t
t 的长度应尽可能长。
输入格式
第一行包含整数 T T T,表示共有 T T T 组测试数据。
每组数据占一行,包含一个字符串 s s s。
输出格式
每组数据输出一行结果,如果
t
t
t 存在,则输出
t
t
t,否则输出 not exist
。
数据范围
前三个测试点满足
1
≤
∣
s
∣
≤
20
1\leq |s|\leq 20
1≤∣s∣≤20。
所有测试点满足
1
≤
T
≤
10
1\leq T\leq 10
1≤T≤10,
1
≤
∣
s
∣
≤
1
0
6
1\leq |s|\leq 10^6
1≤∣s∣≤106。
同一测试点内所有输入字符串
s
s
s 的长度之和不超过
1
0
6
10^6
106。
输入样例1:
2
fixprefixsuffix
abcdabc
输出样例1:
fix
not exist
前置知识:
KMP 算法,算法原理 & 分析见:AcWing 831. KMP字符串。
题目分析:
考虑第
1
,
2
,
4
1,2,4
1,2,4 要求,根据 KMP 算法原理,我们可以得知,当主串在s[i]
和模式串的p[j + 1]
处失配时,要让模式串后移尽量少的长度,也就是说我们需要充分利用模式串的信息,找到一个最大前后缀,然后令j = next[j]
,由于主串和模式串下标均从
1
1
1 开始,故在主串的匹配部分中,这个部分的最大前后缀长度就是next[j]
。依次类推,次大前后缀长度为next[next[j]]
,次次大前后缀为……
挖掘出了条件 1 , 2 , 4 1,2,4 1,2,4 隐藏的性质,我们再来分析条件 3 3 3。
对于主串s
的任何一个前缀
s
[
1
∼
i
]
s[1\sim i]
s[1∼i],其最大匹配后缀长度、次大匹配后缀长度…大小依次为:
\begin{align}
l_1 &= next[i] \\
l_2 &= next[l_1] \\
l_3 &= next[l_2] \\
&…
\end{align}
由上式可知,前i个字母,所有和后缀相等的长度,即每一个 l i l_i li,都是某一个 n e x t next next 的取值。
枚举每一个长度的前后缀,看它是否在中间出现过。
\begin{align}
l_1 &= next[n] \\
l_2 &= next[l_2] \\
l_3 &= next[l_3] \\
&…
\end{align}
若
l
1
l_1
l1 在中间出现过,则必然是某一个
1
∼
k
1\sim k
1∼k 的前后缀,根据上面的分析,就必然是某一个
s
[
l
∼
k
]
s[l\sim k]
s[l∼k] 的
n
e
x
t
next
next 值,即在
n
e
x
t
[
1
]
∼
n
e
x
t
[
n
−
1
]
next[1]\sim next[n - 1]
next[1]∼next[n−1] 中,一定存在一个
n
e
x
t
next
next 满足
n
e
x
t
=
l
1
next = l_1
next=l1,表示在
s
[
1
∼
k
]
s[1\sim k]
s[1∼k] 中,后
l
1
l_1
l1 个字母与前
l
1
l_1
l1 个字母匹配,即表示
l
1
l_1
l1 在中间出现过。
反过来,如果 n e x t = l 1 next = l_1 next=l1,也一定能推出 l 1 l_1 l1 在中间出现过。所以可以得到结论: l 1 l_1 l1 在中间出现过 ⇔ \Leftrightarrow ⇔ n e x t [ 1 ] ∼ n e x t [ n − 1 ] next[1]\sim next[n - 1] next[1]∼next[n−1] 中存在一个 n e x t = l 1 next = l_1 next=l1。
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int ne[N];
char s[N];
bool st[N];
int main()
{
int T;
cin >> T;
int n = 0;
while (T -- )
{
scanf("%s", s + 1);
n = strlen(s + 1);
// 初始化next数组
for (int i = 2, j = 0; i <= n; i ++ )
{
while (j && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) j ++ ;
ne[i] = j;
}
memset(st, 0, sizeof st);
// 所有在next[1] ~ next[n - 1]中出现过的,全部置为true
for (int i = 1; i < n; i ++ ) st[ne[i]] = true;
int res = 0;
for (int i = ne[n]; i; i = ne[i])
if (st[i])
{
// i实际上是匹配到最后一个位置的下标
res = i;
break;
}
if (!res) puts("not exist");
else
{
// 将最后一个位置的下标的后一位置为0,然后截断输出
s[res + 1] = 0;
printf("%s\n", s + 1);
}
}
return 0;
}