AtCoder ARC175F Append Same Characters 题解
题意
给出由
- 选择一个小写英文字母,并在序列中 每个 字符串末尾添加这个字符;
- 交换序列中两个相邻的字符串。
你需要通过若干次操作使得字符串序列的字典序 单调不降,求最小操作次数。
思路
首先发现我们肯定会先做完操作 1 再做操作 2,并且操作 2 的次数等于序列的逆序对数,所以问题转化为在每个串后面加一个字符串
于是我们先计算出原序列的逆序对个数,然后再考虑
考虑怎样的两个字符串
记
然后似乎就做不下去了。这时需要用到一些结论,这里先不给出证明,在文末统一给出证明(下同)。
引理 1:
有了这个之后就有了一个大致思路。我们把所有
那么怎么对
引理 2:
然后就可以很容易用 SA 做到
然后考虑一个
由于
最后还一个问题就是,假如我们枚举了
引理 3: 如果
,那么 。
然后就很容易用 SA 或哈希解决这个问题。具体来说就是把所有串拼在一起并用一个特殊字符隔开,然后就可以用 height
来
还有一点细节需要注意。比如 z
的字符(如果没有就说明不可能找到一个
总时间复杂度为
代码
代码
#include <bits/stdc++.h>
typedef long long LL;
const int N = 3e5 + 5;
int n;
int st[N], len[N];
int L;
char s[2 * N];
int sa[2 * N << 1], rk[2 * N << 1], tot[2 * N], tp[2 * N], ht[2 * N];
void get_SA() {
int m = std::max('z', '#');
auto basic_sort = [&]() {
for(int i = 1; i <= m; i++) tot[i] = 0;
for(int i = 1; i <= L; i++) tot[rk[i]]++;
for(int i = 1; i <= m; i++) tot[i] += tot[i - 1];
for(int i = L; i >= 1; i--) sa[tot[rk[tp[i]]]--] = tp[i];
};
for(int i = 1; i <= L; i++) rk[i] = s[i], tp[i] = i;
basic_sort();
for(int i = 1, p = 0; p < L; i <<= 1, m = p) {
p = 0;
for(int j = 1; j <= i; j++) tp[++p] = L - i + j;
for(int j = 1; j <= L; j++) if(sa[j] > i) tp[++p] = sa[j] - i;
basic_sort();
for(int j = 1; j <= L; j++) tp[j] = rk[j];
p = rk[sa[1]] = 1;
for(int j = 2; j <= L; j++)
rk[sa[j]] = (tp[sa[j - 1]] == tp[sa[j]] && tp[sa[j - 1] + i] == tp[sa[j] + i] ? p : ++p);
}
for(int i = 1, k = 0; i <= L; i++) {
if(rk[i] == 1) { ht[rk[i]] = 0; continue; }
if(k) k--;
int j = sa[rk[i] - 1];
while(i + k <= L && j + k <= L && s[i + k] == s[j + k]) k++;
ht[rk[i]] = k;
}
}
struct SparseTable {
int go[21][2 * N];
void init() {
for(int i = 1; i <= L; i++) go[0][i] = ht[i];
for(int j = 1; j <= 20; j++)
for(int i = 1; i + (1 << j) - 1 <= L; i++)
go[j][i] = std::min(go[j - 1][i], go[j - 1][i + (1 << (j - 1))]);
}
int get(int l, int r) {
if(l == r) return std::min(L - sa[l] + 1, L - sa[r] + 1);
if(l > r) std::swap(l, r);
int k = 31 ^ __builtin_clz(r - l);
return std::min(go[k][l + 1], go[k][r - (1 << k) + 1]);
}
} ST;
int trie[2 * N][26], dep[2 * N], sz[2 * N];
std::vector<int> end[2 * N];
int ctrie;
struct String { int l, r; };
int comp(int i, int j, int k) {
int lc = ST.get(rk[i], rk[j]);
if(lc >= k) return 0;
else return s[i + lc] < s[j + lc] ? -1 : 1;
}
bool operator<(String x, String y) {
int lenx = x.r - x.l + 1, leny = y.r - y.l + 1;
int ret = comp(x.l, y.l, std::min(lenx, leny));
if(ret) return ret == -1;
ret = comp(lenx > leny ? x.l + leny : y.l, leny > lenx ? y.l + lenx : x.l, std::max(lenx, leny) - std::min(lenx, leny));
if(ret) return ret == -1;
ret = comp(lenx > leny ? y.l : y.l + (leny - lenx), leny > lenx ? x.l : x.l + (lenx - leny), std::min(lenx, leny));
return ret == -1;
}
int lcp(int i, int j, int k) {
int lc = ST.get(rk[i], rk[j]);
if(lc >= k) return -1;
else return lc;
}
int lcp(String x, String y) {
int lenx = x.r - x.l + 1, leny = y.r - y.l + 1;
int ret = lcp(x.l, y.l, std::min(lenx, leny));
if(ret != -1) return ret;
ret = lcp(lenx > leny ? x.l + leny : y.l, leny > lenx ? y.l + lenx : x.l, std::max(lenx, leny) - std::min(lenx, leny));
if(ret != -1) return ret + std::min(lenx, leny);
ret = lcp(lenx > leny ? y.l : y.l + (leny - lenx), leny > lenx ? x.l : x.l + (lenx - leny), std::min(lenx, leny));
if(ret != -1) return ret + std::max(lenx, leny);
else return -1;
}
std::vector<int> stk;
std::vector<std::pair<String, LL>> vct;
void dfs(int x) {
if(!end[x].empty()) {
for(int y : stk) {
int j = st[end[x].front()] + len[end[x].front()] - 1;
vct.push_back({{j - (dep[x] - dep[y]) + 1, j}, 0});
for(int i : end[x]) {
auto it = std::lower_bound(end[y].begin(), end[y].end(), i);
vct.back().second += it - end[y].begin();
vct.back().second -= end[y].end() - it;
}
}
stk.emplace_back(x);
}
for(int i = 0; i < 26; i++) if(trie[x][i]) dep[trie[x][i]] = dep[x] + 1, dfs(trie[x][i]);
if(!end[x].empty()) stk.pop_back();
}
int main() {
scanf("%d", &n);
st[1] = 1;
LL base = 0;
for(int i = 1; i <= n; i++) {
scanf("%s", s + st[i]);
len[i] = strlen(s + st[i]);
s[st[i] + len[i]] = '#';
st[i + 1] = st[i] + len[i] + 1;
int now = 0;
for(int j = st[i]; j <= st[i] + len[i] - 1; j++) {
for(int k = s[j] - 'a' + 1; k < 26; k++) base += sz[trie[now][k]];
if(!trie[now][s[j] - 'a']) trie[now][s[j] - 'a'] = ++ctrie;
now = trie[now][s[j] - 'a'];
sz[now]++;
}
for(int k = 0; k < 26; k++) base += sz[trie[now][k]];
end[now].emplace_back(i);
}
L = st[n] + len[n] - 1;
get_SA();
ST.init();
dfs(0);
std::sort(vct.begin(), vct.end());
LL ans = base, sum = 0;
for(int i = 0; i < (int)vct.size() - 1; i++) {
sum += vct[i].second;
int val = lcp(vct[i].first, vct[i + 1].first);
if(val != -1) ans = std::min(ans, base + sum + val + 1);
}
if(!vct.empty()) {
sum += vct.back().second;
int val = 0;
while(vct.back().first.l + val <= vct.back().first.r && s[vct.back().first.l + val] == 'z') val++;
if(vct.back().first.l + val <= vct.back().first.r) ans = std::min(ans, base + sum + val + 1);
}
printf("%lld\n", ans);
return 0;
}
证明
下面的题解其实基本上是官方题解的翻译。
前置
可能需要了解一下 Border 理论和 period 相关的东西。(不用了解太多,知道 Border 和 period 的定义以及弱周期定理就行了,也可以先往下看,看到不懂的再到这篇文章里找)
引理 1
引理 1:
对于
然后对于
引理 2
引理 2:
对于字符串
同时是 的循环节(且
其中
: 根据中国剩余定理,只要
,就一定可以找到 满足 且 ,因为 。所以 ,又因为 也成立,所以同理可得 ,所以 ,即 是 的周期。 同理。
: 不妨设
。由于 ,所以 ,故 是 的 Border,即 是 的循环节。同理,由于 ,所以 ,故 是 的 Border,即 是 的循环节。所以 和 都是 的循环节,根据 弱周期定理, 也是 的循环节。
引理 3
引理 3: 如果
,那么 。
由于
考虑
然后考虑
参考资料
https://www.luogu.com/article/d4y3zqqv
https://atcoder.jp/contests/arc175/editorial/9662
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】