Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Treat one operation as one segment of . A segment is usable exactly when it has Hamming distance at most from some contiguous substring of .
Valid segments are hereditary: every prefix and every suffix of a valid segment is also valid. That is the green light for a farthest-reach greedy, not a giant table.
Fix the current start . For every possible edited position , ask: how far can the block starting at go if the one changed character is exactly ? If even editing cannot reach , then any later edit is already cooked, because it would need an exact match through .
For one fixed replacement character, that question becomes: find the maximum LCP between one modified suffix of and any suffix of . In a sorted suffix array, the best LCP with a query string is attained by the predecessor or successor of the query among suffixes of .
Build a suffix array on s + separator + t plus a sentinel, and RMQ over LCPs. To compare a modified query with a source suffix in , first get the ordinary LCP, then handle whether it stops before, at, or after the edited position.
The operation is exactly this: split into the minimum number of contiguous pieces, where each piece differs from some substring of in at most one character. The chosen source must still be one contiguous substring; you cannot stitch bits of together. That false shortcut is where this problem starts charging rent.
Greedy structure. Call a string segment valid if it can be produced in one operation. If a segment is valid, then every prefix and every suffix of it is also valid: just take the corresponding prefix or suffix of the same source substring, and the number of mismatches cannot increase.
So the usual farthest-prefix greedy is optimal. Suppose the current uncovered position is , and the longest valid segment starting at has length . Any optimal solution starts with some length . Replace that first segment by the longer segment of length . If this eats part of later segments, delete fully eaten ones and trim the first partially eaten one; suffixes of valid segments are valid. The number of operations does not increase. Therefore we should always take the farthest valid first segment.
Now the real job is computing that farthest reach fast.
Scanning one greedy block. Fix the current start . For each possible edited position , define as the maximum length of a prefix of that can match a suffix of if we are allowed to replace exactly character by one lowercase letter. Formally, over all replacement letters and all source positions ,
where is the suffix with position changed to .
While scanning , maintain best = max G_j over processed positions . If , then using the edit at can at least cover the prefix ending at , so the block might continue. If , then editing at cannot even cover .
Could an edit after save us? No. An edit after would require the prefix through to match exactly. But exact matching through is included in the case where the replacement at is just itself. Since even the best replacement at fails, a later edit cannot fix the already-bad prefix. So the farthest valid first block is exactly best, and the greedy takes it.
The loop never moves backward: at the failure position, the previous position was coverable, so best reaches at least the current index. Across one test case, only positions are processed.
Computing one . For a fixed replacement character , we need
Build a suffix array over
where sentinel < sep < letters, and only suffixes starting inside are considered as source suffixes. The separator prevents matches from leaking past the end of .
For any fixed query string , the suffix of with maximum LCP with is one of the two neighbors of in lexicographic order: its predecessor or successor among the sorted source suffixes. This is the standard suffix-array interval fact: strings sharing a prefix form a contiguous interval, so the best prefix match touches the insertion boundary of the query.
So we binary-search the insertion point of among suffixes of , then check the two neighboring suffixes.
Comparing a modified query in .
We never build explicitly. Let rel = i-a, let the source suffix start at , and let the unmodified target suffix start at $y = tBase+a`. Query an ordinary LCP:
There are three cases.
If , the first mismatch is before the edited position. The edit does nothing for the comparison. The LCP length is , and the next characters decide the lexicographic order.
If , the original strings already agree at the edited position. If s[x+rel] != c, the edit creates the first mismatch at rel. Otherwise, the edit changes nothing up to , so the comparison continues exactly as the unmodified one at .
If , the first possible conflict is the edited character. If s[x+rel] != c, stop there. Otherwise, skip that character and add another RMQ LCP from x+rel+1 and y+rel+1.
That gives both the lexicographic comparison and the LCP length in . Binary search costs , and we try all replacement letters.
Complexity. For each test case, suffix array construction and RMQ preprocessing take time and memory for the sparse table. The greedy scan performs positions, each doing binary searches over source suffixes, so the scan costs . The constant is not tiny, but it is controlled; this is exactly the kind of suffix-array nonsense a 3000-rated hard version was hiding.
Research sources checked: official Codeforces editorial page https://codeforces.com/blog/entry/151052, canonical problem page https://codeforces.com/contest/2196/problem/E2, and accepted submission listing https://codeforces.com/submissions/Sanae/page/5. The fetched official page exposes the suffix-array solution but not a prose tutorial, so this explanation is reconstructed from that approach and verified against the supplied statement.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct SuffixArray {
vector<int> a, sa, rank_pos, lcp, lg;
vector<vector<int>> st;
void build(const vector<int>& v) {
a = v;
a.push_back(0);
int n = (int)a.size();
sa.assign(n, 0);
rank_pos.assign(n, 0);
int alphabet = max(30, n + 1);
vector<int> cnt(alphabet, 0);
for (int x : a) cnt[x]++;
for (int i = 1; i < alphabet; i++) cnt[i] += cnt[i - 1];
for (int i = 0; i < n; i++) sa[--cnt[a[i]]] = i;
int classes = 1;
rank_pos[sa[0]] = 0;
for (int i = 1; i < n; i++) {
if (a[sa[i]] != a[sa[i - 1]]) classes++;
rank_pos[sa[i]] = classes - 1;
}
vector<int> sa2(n), rank2(n);
for (int h = 0; (1 << h) < n; h++) {
int shift = 1 << h;
for (int i = 0; i < n; i++) {
sa2[i] = sa[i] - shift;
if (sa2[i] < 0) sa2[i] += n;
}
cnt.assign(classes, 0);
for (int x : rank_pos) cnt[x]++;
vector<int> pos(classes, 0);
for (int i = 1; i < classes; i++) pos[i] = pos[i - 1] + cnt[i - 1];
for (int x : sa2) sa[pos[rank_pos[x]]++] = x;
rank2[sa[0]] = 0;
int new_classes = 1;
for (int i = 1; i < n; i++) {
pair<int, int> cur = {rank_pos[sa[i]], rank_pos[(sa[i] + shift) % n]};
pair<int, int> prv = {rank_pos[sa[i - 1]], rank_pos[(sa[i - 1] + shift) % n]};
if (cur != prv) new_classes++;
rank2[sa[i]] = new_classes - 1;
}
rank_pos.swap(rank2);
classes = new_classes;
}
for (int i = 0; i < n; i++) rank_pos[sa[i]] = i;
build_lcp();
}
void build_lcp() {
int n = (int)a.size();
lcp.assign(max(0, n - 1), 0);
int k = 0;
for (int i = 0; i < n; i++) {
int r = rank_pos[i];
if (r == n - 1) {
k = 0;
continue;
}
int j = sa[r + 1];
while (i + k < n && j + k < n && a[i + k] == a[j + k]) k++;
lcp[r] = k;
if (k) k--;
}
int len = (int)lcp.size();
lg.assign(len + 1, 0);
for (int i = 2; i <= len; i++) lg[i] = lg[i / 2] + 1;
int levels = len ? lg[len] + 1 : 0;
st.assign(levels, vector<int>(len));
if (!len) return;
st[0] = lcp;
for (int p = 1; p < levels; p++) {
int step = 1 << p;
int half = step >> 1;
for (int i = 0; i + step <= len; i++) {
st[p][i] = min(st[p - 1][i], st[p - 1][i + half]);
}
}
}
int get_lcp(int i, int j) const {
if (i == j) return (int)a.size() - i;
int x = rank_pos[i], y = rank_pos[j];
if (x > y) swap(x, y);
int len = y - x;
int p = lg[len];
return min(st[p][x], st[p][y - (1 << p)]);
}
};
void solve() {
int n, m;
cin >> n >> m;
string s, t;
cin >> s >> t;
vector<int> text;
text.reserve(n + m + 1);
for (char c : s) text.push_back(c - 'a' + 2);
text.push_back(1);
for (char c : t) text.push_back(c - 'a' + 2);
SuffixArray suf;
suf.build(text);
vector<int> from_s;
from_s.reserve(n);
for (int pos : suf.sa) {
if (pos < n) from_s.push_back(pos);
}
int t_base = n + 1;
auto finish_compare = [](int x, int y, int len) -> pair<int, int> {
if (x < y) return {1, len};
return {-1, len};
};
auto compare_modified = [&](int s_pos, int start, int pos, int ch) -> pair<int, int> {
int rel = pos - start;
int t_pos = t_base + start;
int first = suf.get_lcp(s_pos, t_pos);
if (first < rel) {
return finish_compare(suf.a[s_pos + first], suf.a[t_pos + first], first);
}
if (first > rel) {
int source_char = suf.a[s_pos + rel];
if (source_char != ch) {
return finish_compare(source_char, ch, rel);
}
return finish_compare(suf.a[s_pos + first], suf.a[t_pos + first], first);
}
int source_char = suf.a[s_pos + rel];
if (source_char != ch) {
return finish_compare(source_char, ch, rel);
}
int second = suf.get_lcp(s_pos + rel + 1, t_pos + rel + 1);
int len = rel + 1 + second;
return finish_compare(suf.a[s_pos + len], suf.a[t_pos + len], len);
};
auto max_len_with_edit = [&](int start, int pos, int ch) {
int lo = 0, hi = (int)from_s.size();
while (lo < hi) {
int mid = (lo + hi) / 2;
if (compare_modified(from_s[mid], start, pos, ch).first == -1) {
hi = mid;
} else {
lo = mid + 1;
}
}
int best = 0;
if (lo > 0) best = max(best, compare_modified(from_s[lo - 1], start, pos, ch).second);
if (lo < (int)from_s.size()) best = max(best, compare_modified(from_s[lo], start, pos, ch).second);
return best;
};
int ans = 0;
int start = 0;
int best = -1;
for (int i = 0; i < m; i++) {
int here = 0;
for (int ch = 2; ch < 28; ch++) {
here = max(here, max_len_with_edit(start, i, ch));
}
best = max(best, here);
if (here < i - start + 1) {
ans++;
start += best;
best = -1;
i = start - 1;
}
}
if (start < m) ans++;
cout << ans << '\n';
}
int main() {
setIO();
int tc;
cin >> tc;
while (tc--) solve();
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct SuffixArray {
vector<int> a, sa, rank_pos, lcp, lg;
vector<vector<int>> st;
void build(const vector<int>& v) {
a = v;
a.push_back(0);
int n = (int)a.size();
sa.assign(n, 0);
rank_pos.assign(n, 0);
int alphabet = max(30, n + 1);
vector<int> cnt(alphabet, 0);
for (int x : a) cnt[x]++;
for (int i = 1; i < alphabet; i++) cnt[i] += cnt[i - 1];
for (int i = 0; i < n; i++) sa[--cnt[a[i]]] = i;
int classes = 1;
rank_pos[sa[0]] = 0;
for (int i = 1; i < n; i++) {
if (a[sa[i]] != a[sa[i - 1]]) classes++;
rank_pos[sa[i]] = classes - 1;
}
vector<int> sa2(n), rank2(n);
for (int h = 0; (1 << h) < n; h++) {
int shift = 1 << h;
for (int i = 0; i < n; i++) {
sa2[i] = sa[i] - shift;
if (sa2[i] < 0) sa2[i] += n;
}
cnt.assign(classes, 0);
for (int x : rank_pos) cnt[x]++;
vector<int> pos(classes, 0);
for (int i = 1; i < classes; i++) pos[i] = pos[i - 1] + cnt[i - 1];
for (int x : sa2) sa[pos[rank_pos[x]]++] = x;
rank2[sa[0]] = 0;
int new_classes = 1;
for (int i = 1; i < n; i++) {
pair<int, int> cur = {rank_pos[sa[i]], rank_pos[(sa[i] + shift) % n]};
pair<int, int> prv = {rank_pos[sa[i - 1]], rank_pos[(sa[i - 1] + shift) % n]};
if (cur != prv) new_classes++;
rank2[sa[i]] = new_classes - 1;
}
rank_pos.swap(rank2);
classes = new_classes;
}
for (int i = 0; i < n; i++) rank_pos[sa[i]] = i;
build_lcp();
}
void build_lcp() {
int n = (int)a.size();
lcp.assign(max(0, n - 1), 0);
int k = 0;
for (int i = 0; i < n; i++) {
int r = rank_pos[i];
if (r == n - 1) {
k = 0;
continue;
}
int j = sa[r + 1];
while (i + k < n && j + k < n && a[i + k] == a[j + k]) k++;
lcp[r] = k;
if (k) k--;
}
int len = (int)lcp.size();
lg.assign(len + 1, 0);
for (int i = 2; i <= len; i++) lg[i] = lg[i / 2] + 1;
int levels = len ? lg[len] + 1 : 0;
st.assign(levels, vector<int>(len));
if (!len) return;
st[0] = lcp;
for (int p = 1; p < levels; p++) {
int step = 1 << p;
int half = step >> 1;
for (int i = 0; i + step <= len; i++) {
st[p][i] = min(st[p - 1][i], st[p - 1][i + half]);
}
}
}
int get_lcp(int i, int j) const {
if (i == j) return (int)a.size() - i;
int x = rank_pos[i], y = rank_pos[j];
if (x > y) swap(x, y);
int len = y - x;
int p = lg[len];
return min(st[p][x], st[p][y - (1 << p)]);
}
};
void solve() {
int n, m;
cin >> n >> m;
string s, t;
cin >> s >> t;
vector<int> text;
text.reserve(n + m + 1);
for (char c : s) text.push_back(c - 'a' + 2);
text.push_back(1);
for (char c : t) text.push_back(c - 'a' + 2);
SuffixArray suf;
suf.build(text);
vector<int> from_s;
from_s.reserve(n);
for (int pos : suf.sa) {
if (pos < n) from_s.push_back(pos);
}
int t_base = n + 1;
auto finish_compare = [](int x, int y, int len) -> pair<int, int> {
if (x < y) return {1, len};
return {-1, len};
};
auto compare_modified = [&](int s_pos, int start, int pos, int ch) -> pair<int, int> {
int rel = pos - start;
int t_pos = t_base + start;
int first = suf.get_lcp(s_pos, t_pos);
if (first < rel) {
return finish_compare(suf.a[s_pos + first], suf.a[t_pos + first], first);
}
if (first > rel) {
int source_char = suf.a[s_pos + rel];
if (source_char != ch) {
return finish_compare(source_char, ch, rel);
}
return finish_compare(suf.a[s_pos + first], suf.a[t_pos + first], first);
}
int source_char = suf.a[s_pos + rel];
if (source_char != ch) {
return finish_compare(source_char, ch, rel);
}
int second = suf.get_lcp(s_pos + rel + 1, t_pos + rel + 1);
int len = rel + 1 + second;
return finish_compare(suf.a[s_pos + len], suf.a[t_pos + len], len);
};
auto max_len_with_edit = [&](int start, int pos, int ch) {
int lo = 0, hi = (int)from_s.size();
while (lo < hi) {
int mid = (lo + hi) / 2;
if (compare_modified(from_s[mid], start, pos, ch).first == -1) {
hi = mid;
} else {
lo = mid + 1;
}
}
int best = 0;
if (lo > 0) best = max(best, compare_modified(from_s[lo - 1], start, pos, ch).second);
if (lo < (int)from_s.size()) best = max(best, compare_modified(from_s[lo], start, pos, ch).second);
return best;
};
int ans = 0;
int start = 0;
int best = -1;
for (int i = 0; i < m; i++) {
int here = 0;
for (int ch = 2; ch < 28; ch++) {
here = max(here, max_len_with_edit(start, i, ch));
}
best = max(best, here);
if (here < i - start + 1) {
ans++;
start += best;
best = -1;
i = start - 1;
}
}
if (start < m) ans++;
cout << ans << '\n';
}
int main() {
setIO();
int tc;
cin >> tc;
while (tc--) solve();
}