[Luogu P4022] [BZOJ 2806] [CTSC2012]熟悉的文章

36 篇文章 1 订阅
11 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

阿米巴是小强的好朋友。

在小强眼中,阿米巴是一个作文成绩很高的文艺青年。为了获取考试作文的真谛,小强向阿米巴求教。阿米巴给小强展示了几篇作文,小强觉得这些文章怎么看怎么觉得熟悉,仿佛是某些范文拼拼凑凑而成的。小强不禁向阿米巴投去了疑惑的眼光,却发现阿米巴露出了一个狡黠的微笑。

为了有说服力地向阿米巴展示阿米巴的作文是多么让人觉得“眼熟”,小强想出了一个评定作文 “熟悉程度”的量化指标:L 0 .小强首先将作文转化成一个 01 串。之后,小强搜集了各路名家的文章,同样分别转化成 01 串后,整理出一个包含了 M M M 个 01 串的“ 标准作文库 ”。

小强认为:如果一个 01 串长度不少于 L L L 且在 标准作文库 中的某个串里出现过(即,它是 标准作文库 的 某个串 的一个 连续子串 ),那么它是“ 熟悉 ”的。对于一篇作文(一个 01 串) A A A,如果能够把 A A A 分割成若干段子串,其中“ 熟悉 ” 的子串的 长度 总 和 不少于 A 总 长度的 90 % 90\% 90%,那么称 A A A 是 “ 熟悉的文章 ”。 L 0 是 能够让 A A A 成为 “ 熟悉的文章 ” 的 所有 L L L 的最大值 (如果不存在这样的 L L L,那么规定 L 0 =0)。

举个例子:

小强的作文库里包含了如下 2 2 2 个字符串:

10110
000001110

有一篇待考察的作文是:

1011001100

小强计算出这篇作文 L L L 的最大值是 4 4 4,因为待考察的作文可以视作’10110’+‘0110’+‘0’,其中’10110’和’0110’被判定为 “ 熟悉 ” 的。而当 L = 5 L = 5 L=5 或是更大的时候,不存在符合题意的分割方法。所以,这篇作文的 L 0 = 4。小强认为阿米巴作文的 L 0 值比其他同学的明显要大。请你帮他验证一下。

输入输出格式

输入格式:

输入文件 cheat.in 第一行是两个整数 N , M N, M N,M,表示待检查的作文数量,和小强的标准作文库的行数。

接下来是 M M M 行的 01 串,表示标准作文库。

接下来是 N N N 行的 01 串,表示 N N N 篇作文。

输出格式:

输出文件 cheat.out 包含 N N N 行,每一行包含一个整数,表示该篇作文的 L 0 值。

输入输出样例

输入样例#1:
1 2
10110
000001110
1011001100
输出样例#1:
4

说明

对于 30 % 30\% 30%的测试数据,输入文件的长度不超过 1000 字节。

对于 50 % 50\% 50%的测试数据,输入文件的长度不超过 61000 字节。

对于 80 % 80\% 80%的测试数据,输入文件的长度不超过 250000 字节。

对于 100 % 100\% 100%的测试数据,输入文件的长度不超过 1100000 字节。

解题分析

首先还是很明显的二分答案, 转化为判定性问题。

m x l e n [ i ] mxlen[i] mxlen[i]为作文串长度为 i i i的前缀能匹配到的最长后缀(这个在广义 S A M SAM SAM上跑一跑就出来了), d p [ i ] dp[i] dp[i]表示作文串前 i i i位能匹配上的最大长度, l l l为二分到的限制, 就有:
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − j ] + j ) ( j ∈ [ m x l e n [ i ] , l ] ) dp[i]=max(dp[i-1],dp[i-j]+j)(j\in [mxlen[i],l]) dp[i]=max(dp[i1],dp[ij]+j)(j[mxlen[i],l])
看起来还不是很明显? 换个写法:
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ j ] + i − j ) ( j ∈ [ i − m x l e n [ i ] , i − l ] ) dp[i]=max(dp[i-1],dp[j]+i-j)(j\in[i-mxlen[i],i-l]) dp[i]=max(dp[i1],dp[j]+ij)(j[imxlen[i],il])
发现 i − m x l e n [ i ] i-mxlen[i] imxlen[i]是单调不减的, d p [ j ] − j dp[j]-j dp[j]j i i i无关, 所以维护一个单调队列即可。

设作文总长度为 S S S, 总复杂度 O ( S l o g S ) O(Slog S) O(SlogS)

代码如下:

#include <cstdio>
#include <cmath>
#include <cctype>
#include <cstring>
#include <cmath>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define ll long long
#define MX 3005000
template <class T> IN T max(T a, T b) {return a > b ? a : b;}
int last, cur, cnt, l, head, tail, n, m;
int to[MX][2], par[MX], len[MX], mxlen[MX], que[MX], dp[MX];
char str[MX];
IN void insert(R int id)
{
	R int now = last, tar;
	if (to[now][id])
	{
		tar = to[now][id];
		if (len[tar] == len[now] + 1) return last = tar, void();
		int nw = ++cnt; len[nw] = len[now] + 1;
		par[nw] = par[tar], par[tar] = nw;
		std::memcpy(to[nw], to[tar], sizeof(to[nw]));
		for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw;
		return last = nw, void();
	}
	else
	{
		cur = ++cnt; len[cur] = len[now] + 1; last = cur;
		for (; (~now) && !to[now][id]; now = par[now]) to[now][id] = cur;
		if (now < 0) return par[cur] = 0, void();
		tar = to[now][id];
		if (len[tar] == len[now] + 1) return par[cur] = tar, void();
		int nw = ++cnt; len[nw] = len[now] + 1;
		par[nw] = par[tar], par[tar] = par[cur] = nw;
		std::memcpy(to[nw], to[tar], sizeof(to[nw]));
		for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw;
	}
}
IN void find()
{
	l = std::strlen(str + 1);
	R int now = 0, ans = 0, id;
	for (R int i = 1; i <= l; ++i)
	{
		id = str[i] - '0';
		if (to[now][id]) ++ans, now = to[now][id];
		else
		{
			W ((~now) && (!to[now][id])) now = par[now];
			if (now < 0) now = ans = 0;
			else ans = len[now] + 1, now = to[now][id];
		}
		mxlen[i] = ans;
	}
}
IN bool check(R int lim)
{
	head = 0, tail = -1;
	for (R int i = 0; i < lim; ++i) dp[i] = 0;
	for (R int i = lim; i <= l; ++i)
	{
		dp[i] = dp[i - 1];
		W (head <= tail && dp[i - lim] - (i - lim) > dp[que[tail]] - que[tail]) --tail;
		que[++tail] = i - lim;
		W (head <= tail && que[head] < i - mxlen[i]) ++head;
		if (head <= tail) dp[i] = max(dp[i], dp[que[head]] - que[head] + i);
	}
	return dp[l] * 10 >= l * 9;
}
int main(void)
{
	scanf("%d%d", &n, &m); par[0] = -1;
	for (R int i = 1; i <= m; ++i)
	{
		scanf("%s", str + 1);
		l = std::strlen(str + 1); last = 0;
		for (R int j = 1; j <= l; ++j)
		insert(str[j] - '0');
	}
	for (R int i = 1; i <= n; ++i)
	{
		scanf("%s", str + 1); find();
		int lef = 1, rig = l, mid, ans = 0;
		W (lef <= rig)
		{
			mid = lef + rig >> 1;
			if (check(mid)) ans = mid, lef = mid + 1;
			else rig = mid - 1;
		}
		printf("%d\n", ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值