CF955D Scissors
CF955D Scissors
题目来源:Codeforces, Codeforces Round #471 (Div. 2), CF#471, CF955D Scissors
题目大意
给定一个长度为 \(n\) 的串 \(s\) 和一个长度为 \(m\) 的串 \(t\)。给定一个正整数 \(k\)。
请找出 \(s\) 的两个互不重叠的、长度为 \(k\) 的子串,满足:将它们按原有顺序拼接后,得到的串中包含 \(t\)(\(t\) 是一个子串)。
数据范围:\(2\leq m\leq 2\cdot k\leq n\leq 5\times 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]\)。
如果存在一对 \(k\leq i < j\leq n - k + 1\),满足 \(f_i + g_j = m\),那么我们已经找出了答案。
然而,如果没找到这样的 \(i,j\),并不一定代表无解。事实上,上述做法的误区是,数值大的 \(f_i\), \(g_j\),并不一定最优。具体来说,当 \(k\leq i < 2k\) 时,可能存在 \(s[i - f_i +1]\) 的一个 border,由它作为 \(t\) 的前缀,去和后面拼接,能得到答案。对于 \(g\) 也是类似的。
如果枚举 \(i\),再暴力枚举 border,由于 border 的数量最大有 \(\mathcal{O}(\text{串长})\) 个(例如串 \(\texttt{aaa...a}\)),这样时间复杂度最坏为 \(\mathcal{O}(k^2)\),无法通过本题。
考虑优化这个“暴力跳 border”的过程。设 \(t[1,x]\) 的 border 长度为 \(\text{fail}(x)\)(与 KMP 算法里的定义是一样的),对于所有 \(1\leq x\leq m\) 如果 \(\text{fail}(x)\neq 0\),我们从 \(\text{fail}(x)\) 向 \(x\) 连一条边,发现可以得到一个有根树森林。“暴力跳 border”,就相当于在枚举一个节点 \(x\) 的所有祖先。因此可以用树链剖分优化。更具体地,由于一前一后各做一次 KMP,实际上需要建出两个森林。
考虑从小到大枚举 \(j\),每次 \(i = j - 1\) 会成为一个新出现的、可能的 \(i\),我们把 \(f_i\) 在树上的所有祖先打上标记。然后要对 \(g_j\) 的所有祖先进行查询。通过树链剖分和 dfs 序,把树上问题转化为序列问题后,问题可以形式化地描述为:
有两个排列 \(p_{1\dots m}, q_{1\dots m}\)(也就是两个森林的 dfs 序序列),需要支持若干次操作。操作分为如下两种:
- 给定区间 \([l,r]\),把 \(p_{l\dots r}\) 里所有数值打上标记。
- 给定区间 \([l,r]\),查询 \(q_{l\dots r}\) 里是否存在被标记过的数值。
这个问题并不难。考虑离线,预处理出每个数值第一次被标记的时间,这可以通过倒序遍历操作,转化为区间覆盖问题。然后查询就变成了区间最小值查询,可以用 ST 表实现。
如果用线段树做区间覆盖,因为外层还要跳 \(\mathcal{O}(\log n)\) 条重链,总时间复杂度是 \(\mathcal{O}(n\log^2 n)\)(\(n,m,k\) 同阶)。可以通过本题,但不够优秀。进一步观察发现,区间覆盖操作是静态的(所有修改发生在询问之前),因此可以用并查集维护。总时间复杂度优化为 \(\mathcal{O}(n\log 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 << " \n"[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 << " \n"[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;
}