題意
給定 \(n\) 個字串 \(s_1, s_2, ..., s_n\)。
你需要在其中選擇一些字串,按照順序拼接。
在所有生成的長度為 \(k\) 的字串中,選擇字典序最小的一個。
\(n \le 2000, k \le 10 ^ 4, \sum |s_i| \le 10 ^ 6\)
Sol
考慮一個樸素的 dp。
設 \(f_{i, j}\) 表示前 \(i\) 個字串,放到了前 \(j\) 個位置的最小的字串。
這個 dp 的複雜度為 \(nk \times \sum |s_i|\)。
不難注意到一個事情,如果當前放的字串 \(s_i\) 已經不是最優的字典序了,那麼無論後面怎麼放,此處放 \(s_i\) 都不可能成為最優方案。
考慮想的暴力一點:
設 \(f_{i, j, k}\) 表示用第 \(j\) 個字串的第 \(k\) 位填在第 \(i\) 位,是否可以為當前字典序最小的方案,且合法。
先考慮簡單的合法,每次新放一個字串都需要判斷放當前字串是否可以組成長度為 \(k\) 的答案。
設 \(g_{i, j}\) 表示用第 \(j\) 個字串填完前 \(i\) 位是否能合法。
顯然:
\(g_{i, j}\) 顯然可以在 \(O(nk)\) 的時間求出。
考慮當前的最小字元。
注意到當前可以填的最小字元 \(s_{j, k}\) 滿足:
注意到下面的式子和 \(g\) 類似,可以在 \(O(nk)\) 之內找出最小字元 \(c\)。
考慮上面的式子。
考慮直接把 \(j, k\) 兩維暴力壓成一維後用 \(\text{bitset}\) 維護。
二分當前的最小字元 \(c\),考慮預處理 \(h_{i, j}\) 表示第 \(j\) 個位置存在字典序小於等於 \(i\) 字元。
那麼一個很顯然的事情,當前最小字元為 \(c\) 合法,當且僅當 (h[c] & (f[i - 1] << 1))
的 \(\text{bitset}\) 存在 \(1\)。
考慮二分 \(c\),複雜度:\(O(\frac{n \times \sum |s_i|}{\omega} \times \log 26)\)。
那麼轉移就很簡單了。
實現細節較多。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <array>
#include <string>
#include <bitset>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;
#endif
int read() {
int p = 0, flg = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flg = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
p = p * 10 + c - '0';
c = getchar();
}
return p * flg;
}
string read_() {
string ans;
char c = getchar();
while (c < 'a' || c > 'z') c = getchar();
while (c >= 'a' && c <= 'z') ans += c, c = getchar();
return ans;
}
void write(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
bool _stmer;
const int N = 2e3 + 5, M = 1e6 + 5, K = 1e4 + 5;
array <int, N> len, fir, sum;
array <bitset <N>, K> g, suf;
array <bitset <M>, 27> h;
array <bitset <M>, 2> f;
bitset <M> pre, vis;
bool check(int v, int c) { return (h[c] & (f[v] << 1)).count(); }
bool _edmer;
int main() {
cerr << (&_stmer - &_edmer) / 1024.0 / 1024.0 << "MB\n";
/* #ifdef cxqghzj */
/* freopen("_.txt", "r", stdin); */
/* #endif */
int n = read(), m = read();
string mp;
/* write(f[0].count()), puts("#"); */
for (int i = 1; i <= n; i++)
vis[fir[i] = mp.size()] = 1, mp += read_(), len[i] = (sum[i] = mp.size()) - fir[i];
for (int i = 1; i <= 26; i++)
for (int j = 0; j < (int)mp.size(); j++)
if (!vis[j])
h[i][j + 1] = (mp[j] <= ('a' + i - 1));
suf[m + 1][n + 1] = 1;
#define upd(x, y) (x = x | y)
for (int j = n; j; j--) {
for (int i = 1; i + len[j] - 1 <= m; i++)
upd(g[i][j], suf[i + len[j]][j + 1]);
for (int i = 1; i <= m + 1; i++) suf[i][j] = g[i][j] | suf[i][j + 1];
}
f[0][0] = 1, pre[1] = 1;
int v = 0;
string ans;
for (int i = 0; i < m; i++) {
int l = 1, r = 26;
for (int j = 1; j <= n; j++) {
if (pre[j] && g[i + 1][j]) r = min(r, mp[fir[j]] - 'a' + 1);
pre[j + 1] = f[v][sum[j]] | pre[j];
}
pre = 0;
while (l < r) {
int mid = (l + r) >> 1;
if (check(v, mid)) r = mid;
else l = mid + 1;
}
ans.push_back(r + 'a' - 1);
f[v ^ 1] = h[r] & (f[v] << 1);
bool flg = 0;
for (int j = 0; j <= n; j++) {
if (j && g[i + 1][j] && mp[fir[j]] <= r + 'a' - 1)
upd(f[v ^ 1][fir[j] + 1], flg);
flg |= f[v][sum[j]];
}
/* write(f[v ^ 1].count()), puts(""); */
v ^= 1;
}
printf("%s\n", ans.c_str());
return 0;
}