CF955D Scissors
题目来源:Codeforces, Codeforces Round #471 (Div. 2), CF#471, CF955D Scissors
题目大意
给定一个长度为 (n) 的串 (s) 和一个长度为 (m) 的串 (t)。给定一个正整数 (k)。
请找出 (s) 的两个互不重叠的、长度为 (k) 的子串,满足:将它们按原有顺序拼接后,得到的串中包含 (t)((t) 是一个子串)。
数据范围:(2leq mleq 2cdot kleq nleq 5 imes 10^5)。
本题题解
考虑 (s) 里,被选出的两个长度为 (k) 的子串,称为关键子串 1、关键子串 2。
分两种情况:
- (t) 被完整地包含在某个关键子串中;
- (t) 在两个关键子串的拼接处。
对于情况 1,做一遍普通的 KMP 即可判断。因此以下只讨论情况 2。
枚举关键子串 1 的结尾位置 (i),用 KMP 可以求出一个最大的 (f_i),满足 (s[i - f_i + 1, i] = t[1,f_i])。同理,枚举关键子串 2 的开头位置 (j),可以求出一个最大的 (g_j),满足 (s[j,j + g_j - 1] = t[m - g_j + 1, m])。
如果存在一对 (kleq i < jleq n - k + 1),满足 (f_i + g_j = m),那么我们已经找出了答案。
然而,如果没找到这样的 (i,j),并不一定代表无解。事实上,上述做法的误区是,数值大的 (f_i), (g_j),并不一定最优。具体来说,当 (kleq i < 2k) 时,可能存在 (s[i - f_i +i]) 的一个 border,由它作为 (t) 的前缀,去和后面拼接,能得到答案。对于 (g) 也是类似的。
如果枚举 (i),再暴力枚举 border,由于 border 的数量最大有 (mathcal{O}( ext{串长})) 个(例如串 ( exttt{aaa...a})),这样时间复杂度最坏为 (mathcal{O}(km)),无法通过本题。
考虑优化这个“暴力跳 border”的过程。设 (t[1,x]) 的 border 长度为 ( ext{fail}(x))(与 KMP 算法里的定义是一样的),对于所有 (1leq xleq m) 如果 ( ext{fail}(x) eq 0),我们从 ( ext{fail}(x)) 向 (x) 连一条边,发现可以得到一个有根树森林。“暴力跳 border”,就相当于在枚举一个节点 (x) 的所有祖先。因此可以用树链剖分优化。更具体地,由于一前一后各做一次 KMP,实际上需要建出两个森林。
考虑从小到大枚举 (j),每次 (i = j - 1) 会成为一个新出现的、可能的 (i),我们把 (f_i) 在树上的所有祖先打上标记。然后要对 (g_j) 的所有祖先进行查询。通过树链剖分和 dfs 序,把树上问题转化为序列问题后,问题可以形式化地描述为:
有两个排列 (p_{1dots m}, q_{1dots m})(也就是两棵树的 dfs 序序列),需要支持若干次操作。操作分为如下两种:
- 给定区间 ([l,r]),把 (p_{ldots r}) 里所有数值打上标记。
- 给定区间 ([l,r]),查询 (q_{ldots r}) 里是否存在被标记过的数值。
这个问题并不难。考虑离线,预处理出每个数值第一次被标记的时间,这可以通过倒序遍历操作,转化为区间覆盖问题。然后查询就变成了区间最小值查询,可以用 ST 表实现。
如果用线段树做区间覆盖,因为外层还要跳 (mathcal{O}(log n)) 条重链,总时间复杂度是 (mathcal{O}(nlog^2 n))((n,m,k) 同阶)。可以通过本题,但不够优秀。进一步观察发现,区间覆盖操作是静态的(所有修改发生在询问之前),因此可以用并查集维护。总时间复杂度优化为 (mathcal{O}(nlog n))。
参考代码
建议使用快速输入、输出,详见本博客公告。
// problem: CF955D
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 5e5;
const int LOG = 18;
const int INF = 1e9;
class KMP {
public:
int fail[MAXN + 5], match[MAXN + 5];
void get_fail(const char* t, int m) {
fail[1] = 0;
for (int i = 2, j = 0; i <= m; ++i) {
while (j && t[i] != t[j + 1])
j = fail[j];
if (t[i] == t[j + 1])
++j;
fail[i] = j;
// cerr << j << "
"[i == m];
}
}
void get_match(const char* s, const char* t, int n, int lim) {
for (int i = 1, j = 0; i <= n; ++i) {
while (j && (s[i] != t[j + 1] || j >= lim))
j = fail[j];
if (s[i] == t[j + 1])
++j;
match[i] = j;
// cerr << j << "
"[i == n];
}
}
KMP() {}
};
class Tree {
public:
struct EDGE { int nxt, to; } edge[MAXN * 2 + 5];
int head[MAXN + 5], tot;
inline void add_edge(int u, int v) { edge[++tot].nxt = head[u]; edge[tot].to = v; head[u] = tot; }
int fa[MAXN + 5], sz[MAXN + 5], son[MAXN + 5], dep[MAXN + 5];
void dfs1(int u) {
sz[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa[u])
continue;
fa[v] = u;
dep[v] = dep[u] + 1;
dfs1(v);
sz[u] += sz[v];
if (!son[u] || sz[v] > sz[son[u]])
son[u] = v;
}
}
int top[MAXN + 5], dfn[MAXN + 5], ofn[MAXN + 5], rev[MAXN + 5], cnt_dfn;
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt_dfn;
rev[cnt_dfn] = u;
if (son[u])
dfs2(son[u], t);
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
ofn[u] = cnt_dfn;
}
bool is_anc(int anc, int v) {
return dfn[anc] <= dfn[v] && ofn[anc] >= dfn[v];
}
Tree() {}
};
class StaticRangeCover {
private:
int len;
int fa[MAXN + 5], sz[MAXN + 5], mx[MAXN + 5], res[MAXN + 5];
vector<pair<pii, int> > vec;
int get_fa(int u) {
return (u == fa[u]) ? u : (fa[u] = get_fa(fa[u]));
}
void unite(int u, int v) {
u = get_fa(u);
v = get_fa(v);
if (u != v) {
if (sz[u] > sz[v])
swap(u, v);
fa[u] = v;
sz[v] += sz[u];
mx[v] = max(mx[v], mx[u]);
}
}
void jump_and_cover(int l, int r, int val) {
for (int i = l; i <= r; ++i) {
i = mx[get_fa(i)];
if (i > r) {
break;
}
res[i] = val;
unite(i, r + 1);
}
}
public:
void build(int _len) {
len = _len;
for (int i = 1; i <= len + 1; ++i) {
fa[i] = i;
sz[i] = 1;
mx[i] = i;
res[i] = INF;
}
}
void range_cover(int l, int r, int v) {
vec.pb(mk(mk(l, r), v));
}
void get_res(int* _res) {
for (int i = SZ(vec) - 1; i >= 0; --i) {
jump_and_cover(vec[i].fi.fi, vec[i].fi.se, vec[i].se);
}
for (int i = 1; i <= len; ++i) {
_res[i] = res[i];
}
}
StaticRangeCover() {}
};
class RangeMinQuery {
private:
int _log2[MAXN + 5];
pii st[MAXN + 5][LOG + 1];
public:
void build(int* a, int n) {
_log2[0] = -1;
for (int i = 1; i <= n; ++i) {
_log2[i] = _log2[i >> 1] + 1;
st[i][0] = mk(a[i], i);
}
for (int j = 1; j <= LOG; ++j) {
for (int i = 1; i + (1 << (j - 1)) <= n; ++i) {
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
pii rmq(int l, int r) {
int k = _log2[r - l + 1];
return min(st[l][k], st[r - (1 << k) + 1][k]);
}
int rmq_val(int l, int r) { return rmq(l, r).fi; }
int rmq_pos(int l, int r) { return rmq(l, r).se; }
RangeMinQuery() {}
};
int n, m, K;
char s[MAXN + 5], t[MAXN + 5];
KMP kmp, kmp_rev;
Tree tree, tree_rev;
StaticRangeCover DSU;
RangeMinQuery RMQ;
void cover_anc(int u, int tim) {
while (u) {
int l = tree.dfn[tree.top[u]], r = tree.dfn[u];
DSU.range_cover(l, r, tim);
u = tree.fa[tree.top[u]];
}
}
int first_cov_tim[MAXN + 5];
int arr[MAXN + 5];
void query_anc(int u, int tim) {
// u 的祖先, 有没有人在第 tim 时刻之前(严格小于)被覆盖了?
while (u) {
int l = tree_rev.dfn[tree_rev.top[u]], r = tree_rev.dfn[u];
if (RMQ.rmq_val(l, r) < tim) {
cout << "Yes" << endl;
int x = tree_rev.rev[RMQ.rmq_pos(l, r)] - 1;
for (int i = K; i < tim; ++i) {
int v = kmp.match[i];
if (v != 0 && tree.is_anc(x, v)) {
cout << i - K + 1 << " " << tim << endl;
exit(0);
}
}
assert(0);
}
u = tree_rev.fa[tree_rev.top[u]];
}
}
int main() {
cin >> n >> m >> K;
cin >> (s + 1);
cin >> (t + 1);
kmp.get_fail(t, m);
kmp.get_match(s, t, n, min(m, K));
reverse(s + 1, s + n + 1);
reverse(t + 1, t + m + 1);
kmp_rev.get_fail(t, m);
kmp_rev.get_match(s, t, n, min(m, K));
if (m <= K) {
for (int i = m; i <= n - K; ++i) {
if (kmp.match[i] == m) {
cout << "Yes" << endl;
cout << max(1, i - K + 1) << " " << n - K + 1 << endl;
return 0;
}
}
for (int i = K + 1; i <= n - m + 1; ++i) {
if (kmp_rev.match[n - i + 1] == m) {
cout << "Yes" << endl;
cout << 1 << " " << min(i, n - K + 1) << endl;
return 0;
}
}
}
for (int i = 2; i <= m; ++i) {
if (kmp.fail[i]) {
tree.add_edge(kmp.fail[i], i);
}
if (kmp_rev.fail[i]) {
tree_rev.add_edge(m - kmp_rev.fail[i] + 1, m - i + 1);
// cerr << "addedge(rev) " << m - kmp_rev.fail[i] + 1 << " " << m - i + 1 << endl;
}
}
for (int i = 1; i <= m; ++i) {
if (!tree.fa[i]) {
tree.dfs1(i);
tree.dfs2(i, i);
}
}
for (int i = m; i >= 1; --i) {
if (!tree_rev.fa[i]) {
tree_rev.dfs1(i);
tree_rev.dfs2(i, i);
}
}
DSU.build(m);
for (int i = n - K; i >= K; --i) {
if (kmp.match[i] != 0) {
int u = kmp.match[i];
// cerr << "cover " << u << " " << i << endl;
cover_anc(u, i);
}
} // 倒序添加, 预处理出每个位置第一次被覆盖是在什么时间
DSU.get_res(first_cov_tim);
for (int i = 1; i <= m; ++i) {
if (tree_rev.rev[i] != 1) {
arr[i] = first_cov_tim[tree.dfn[tree_rev.rev[i] - 1]];
// cerr << arr[i] << endl;
}
}
RMQ.build(arr, m);
for (int i = K + 1; i <= n - K + 1; ++i) {
if (kmp_rev.match[n - i + 1] != 0) {
int u = m - kmp_rev.match[n - i + 1] + 1;
// cerr << "query " << u << " " << i << endl;
assert(u != 1);
query_anc(u, i);
}
}
cout << "No" << endl;
return 0;
}