【CodeForces 1137B】next数组 | KMP | 公共前后缀 | 简单贪心 | E

我竟然是先学AC自动机再学KMP…qwq

1137B. Camp Schedule

time limit per test: 1 seconds
memory limit per test: 512 megabytes
input: standard input
output: standard output

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 1st5×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+1len(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, j1] 存在着一个最长公共前后缀 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[ilen(R), i1] 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[ilen(R), i1] 的后一个 和 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, j1] 的最长公共前后缀,就可以在 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值