[ARC058F] Iroha Loves Strings 题解

Description

给你 nn 个字符串,请你从中选出若干个,按给出顺序连接起来。选出字符串的总长必须等于 kk,求字典序最小的。保证有解。

1n20001 \leq n \leq 20001k1041\leq k \leq 10^4,字符串总长不超过 10610^6

Solution

显然是个 dp。

有个暴力的想法是设 fi,jf_{i,j} 表示前 ii 个字符串组合成的长度为 jj 的最小字符串,总状态数是 nknk,但是字符串总长为 O(nk2)O(nk^2),过不了。

考虑优化。

先用 01 背包求出后缀能够拼出的所有长度,对于 fi,jf_{i,j},如果 [i+1,n][i+1,n] 这些字符串无法拼出 kjk-j 就把 fi,jf_{i,j} 扔掉。

对于剩下的字符串,如果 fi,jf_{i,j}fi,kf_{i,k} 都不为互相的前缀且 fi,j<fi,kf_{i,j}<f_{i,k},则 fi,kf_{i,k} 无论怎么拼都无法优于 fi,jf_{i,j},可以把 fi,kf_{i,k} 扔掉。

设剩下的最长的字符串为母串,则所有剩下的 fi,jf_{i,j} 一定是母串的前缀,所以维护这个母串和每个 fi,jf_{i,j} 的长度即可让状态能被承受。


然后考虑怎么转移。

先从小到大枚举 jj,则 fi,j=min{fi1,j,fi1,jsi+si}f_{i,j}=\min\left\{f_{i-1,j},f_{i-1,j-|s_i|}+s_i\right\},可以用二分+哈希得到。同时维护一个从栈顶到栈底长度从大到小的单调栈,表示当前求出的有用的状态。

如果 fi,topf_{i,top}fi,jf_{i,j} 的前缀,就直接将 fi,jf_{i,j} 放到栈顶。如果 fi,j>fi,topf_{i,j}>f_{i,top}fi,jf_{i,j} 就没用了。否则就一直弹栈直到栈顶是 fi,jf_{i,j} 的前缀后再把 fi,jf_{i,j} 加到栈里。

时间复杂度:O(nklogk+si)O\left(nk\log k+\sum\left|s_i\right|\right)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <bits/stdc++.h>

// #define int int64_t

using u64 = uint64_t;

const int kMaxN = 2e3 + 5, kMaxK = 1e4 + 5, kMod = 998244353;

int n, k, m, sz;
int len[kMaxN], pos[kMaxK], hs1[kMaxK], pw[kMaxK];
bool vis[kMaxK];
std::pair<int, int> stk[kMaxK];
std::bitset<kMaxK> f[kMaxN];
std::string s[kMaxN], str;
std::vector<int> hs2[kMaxN];

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework() {
for (int i = 1; i <= n; ++i) {
hs2[i].resize(len[i] + 1);
for (int j = 1; j <= len[i]; ++j)
hs2[i][j] = add(13331ll * hs2[i][j - 1] % kMod, s[i][j]);
}
pw[0] = 1;
for (int i = 1; i <= k; ++i) pw[i] = 13331ll * pw[i - 1] % kMod;
}

u64 gethash(int p, int r, int pos, int o = 0) {
if (!o || r <= pos) return hs1[r];
else return add(1ll * hs1[pos] * pw[r - pos] % kMod, hs2[p][r - pos]);
}

bool cmp(int p, std::pair<int, int> p1, std::pair<int, int> p2) { // p1 <= p2
int len1 = p1.first + p1.second * len[p], len2 = p2.first + p2.second * len[p];
int L = 0, R = std::min(len1, len2) + 1, res = 0;
while (L + 1 < R) {
int mid = (L + R) >> 1;
if (gethash(p, mid, p1.first, p1.second) == gethash(p, mid, p2.first, p2.second)) L = res = mid;
else R = mid;
}
if (res == std::min(len1, len2)) return len1 <= len2;
else {
++res;
char c1, c2;
if (res <= p1.first) c1 = str[res];
else c1 = s[p][res - p1.first];
if (res <= p2.first) c2 = str[res];
else c2 = s[p][res - p2.first];
return c1 <= c2;
}
}

bool checkpre(int p, std::pair<int, int> p1, std::pair<int, int> p2) {
int len1 = p1.first + p1.second * len[p];
int len2 = p2.first + p2.second * len[p];
if (len1 > len2) return 0;
return gethash(p, len1, p1.first, p1.second) == gethash(p, len1, p2.first, p2.second);
}

std::pair<int, int> getmin(int p, std::pair<int, int> p1, std::pair<int, int> p2) {
return cmp(p, p1, p2) ? p1 : p2;
}

void dickdreamer() {
std::cin >> n >> k;
for (int i = 1; i <= n; ++i) {
std::cin >> s[i];
len[i] = (int)s[i].size();
s[i] = " " + s[i];
}
prework();
f[n + 1][0] = 1;
for (int i = n; i; --i) {
f[i] = f[i + 1] | (f[i + 1] << len[i]);
}
str = " " + str, pos[++m] = 0, vis[0] = 1;
for (int i = 1; i <= n; ++i) {
int top = 0;
for (int j = 0; j <= k; ++j) {
if (!f[i + 1][k - j]) continue;
std::pair<int, int> now = {0, 0};
if (vis[j]) now = {j, 0};
if (j >= len[i] && vis[j - len[i]]) {
if (!now.first && !now.second) now = {j - len[i], 1};
else now = getmin(i, now, {j - len[i], 1});
}
if (j && !now.first && !now.second) continue;
if (!top) {
stk[++top] = now;
} else {
if (checkpre(i, stk[top], now) || cmp(i, now, stk[top])) {
for (; top && !checkpre(i, stk[top], now); --top) {}
stk[++top] = now;
}
}
}
std::fill_n(vis, k + 1, 0);
m = top;
for (int j = 1; j <= top; ++j) pos[j] = stk[j].first + stk[j].second * len[i];
str.resize(stk[m].first + 1);
if (stk[m].second) {
for (int j = 1; j <= len[i]; ++j) str.push_back(s[i][j]);
}
sz = (int)str.size() - 1;
for (int j = 1; j <= sz; ++j)
hs1[j] = add(13331ll * hs1[j - 1] % kMod, str[j]);
for (int j = 1; j <= m; ++j) vis[pos[j]] = 1;
}
for (int i = 1; i < (int)str.size(); ++i) std::cout << str[i];
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

[ARC058F] Iroha Loves Strings 题解
https://sobaliuziao.github.io/2024/09/29/post/ab6e89d0.html
作者
Egg_laying_master
发布于
2024年9月29日
许可协议