我竟然是先学AC自动机再学KMP…qwq
1137B. Camp Schedule
Tags:KMP 简单贪心 next数组
题目描述
给定两个 01 01 01 串 s s s 和 t t t。 s s s 中的字符可以任意重排。
求使得 t t t 在 s s s 中出现的次数最多的重排(多组解时任意输出一组)。
输入
1 1 1 组。第一行串 s s s,第二行串 t t t。
范围:
1
≤
∣
s
∣
,
∣
t
∣
≤
5
×
1
0
5
1\le |s|,|t| \le 5 \times 10^5
1≤∣s∣,∣t∣≤5×105
输出
输出重排后的
s
s
s,使得
t
t
t 在其中出现次数最多。
输入样例 1
10010110
100011
输出样例 1
01100011
输入样例 2
10
11100
输出样例 2
01
分析
emmmm… 主要还是题面有点长以及样例有点弱,其实这道题考察的 主要是阅读理解 主要是 next数组 求最长公共前后缀。
为什么要找公共前后缀 :
记 t t t 串最长公共前后缀为 p p p,记前缀 p p p 的最后一个字母索引是 i d x idx idx,
再记 t t t 的索引范围为 [ i d x + 1 , l e n ( t ) − 1 ] [idx+1,len(t)-1] [idx+1,len(t)−1] 的子串是 r r r
构造串 a = ( t + r + r + r + . . . ) a = (t+r+r+r+...) a=(t+r+r+r+...),显然此种构造方式是最省字母的让 t t t 多次出现的构造方式。
因此我们将 s s s 重排的目标就是把它弄成 a a a 这种结构,直到 “字符原料” 不足以再拼凑更多的 r r r 串。
然后关键来了:为什么这样对 s s s 重排就一定能使 t t t 出现次数最多呢?
显然,由于重排过程 “字符数量守恒”,如果最省字母的构造方式都不够再让 t t t 出现更多次,那么其他的构造方式就更不可能了
(意会,意会…)
next数组 是个啥玩意 :
这首先得简单提一下 KMP 算法:当我们做字符串匹配的时候,如果匹配到某个位置发现失配了(文本 T [ i ] T[i] T[i] ≠ 模式 P [ j ] P[j] P[j]),
而 P P P 的子串 P [ 0 , j − 1 ] P_{[0,\ j-1]} P[0, j−1] 存在着一个最长公共前后缀 R R R,那么下一步就可以尝试匹配 T [ i ] T[i] T[i] 和 P [ l e n ( R ) ] P[len(R)] P[len(R)] 这两个字符。
也就是说不必再回头比。为什么呢?
因为失配发生在 T [ i ] T[i] T[i] 和 P [ j ] P[j] P[j],说明在这之前是匹配 ok 的,所以 T [ i − l e n ( R ) , i − 1 ] T_{[i-len(R),\ i-1]} T[i−len(R), i−1] 是 R R R 。而 P [ 0 , l e n ( R ) − 1 ] P_{[0,\ len(R)-1]} P[0, len(R)−1] 也是 R R R,
所以我们完全可跳过这段,下一步就比 T [ i − l e n ( R ) , i − 1 ] T_{[i-len(R),\ i-1]} T[i−len(R), i−1] 的后一个 和 P [ 0 , l e n ( R ) − 1 ] P_{[0,\ len(R)-1]} P[0, len(R)−1] 的后一个,也就是 T [ i ] T[i] T[i] 和 P [ l e n ( R ) ] P[len(R)] P[len(R)]。
因此,如果我们知道任何一个 P [ 0 , j − 1 ] P_{[0,\ j-1]} P[0, j−1] 的最长公共前后缀,就可以在 O ( l e n ( T ) ) O(len(T)) O(len(T)) 内完成模式匹配。
那要如何知道呢?就是通过 next数组 知道的。
那怎么求这个数组呢?一句话就是我配我自己。这个博文很多,就不作多言了(也许之后会补上~)
时间复杂度:
- 求 next数组, O ( l e n ( t ) ) O(len(t)) O(len(t)) 的。
- 统计 s s s 可用字符 “原料” 数目: O ( l e n ( s ) ) O(len(s)) O(len(s)) 的 。
- 拼凑过程: O ( l e n ( s ) ) O(len(s)) O(len(s)) 的
- 总时间复杂度: O ( l e n ( t ) + l e n ( s ) ) O(len(t)+len(s)) O(len(t)+len(s))
AC代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define PC putchar
#define CON constexpr
CON int MN(1e6+7);
int next[MN];
char s[MN], t[MN];
char stack[MN];
int top;
void count01(int &rest_0, int &rest_1)
{
for (char *p=s; *p; ++p)
if (*p == '0') ++rest_0;
else ++rest_1;
for (char *p=t; *p; ++p)
if (*p == '0') --rest_0;
else --rest_1;
}
int get_next(const char *s, int *next)
{
int i = 0, j = -1, len = strlen(s);
next[0] = -1;
while (i<len)
{
if (j==-1 || s[i]==s[j])
next[++i] = ++j;
else
j = next[j];
}
return len;
}
int main()
{
scanf("%s %s", s, t);
int rest_0 = 0, rest_1 = 0;
count01(rest_0, rest_1);
if (rest_0 < 0 || rest_1 < 0)
return printf(s), 0;
for (char *p=t; *p; ++p)
stack[top++] = *p;
int len = get_next(t, next);
char *begin = t + next[len];
bool ok = true;
while (ok)
{
for(char *p=begin; *p; stack[top++]=*p++)
{
ok = false;
if (*p=='0' && rest_0)
--rest_0, ok = true;
else if (*p=='1' && rest_1)
--rest_1, ok = true;
if (!ok)
break;
}
}
printf(stack);
while (rest_0--)
PC('0');
while (rest_1--)
PC('1');
return 0;
}