dp(i, j)表示考虑前i个串,拼成长度为j的字典序最小串。
显然这样状态数n * m,每个状态是一个长度为m的string,不可行。
发现一个性质,如果当前有字典序 dp(i, j) < dp(i, k)【此处的小于定义为严格存在第一个字符不同位置的小于,不包括dp(i, j)是dp(i, k)前缀或相反,之后的比较也是这样】且(i + 1 ~ n)能够拼出一个长度m - j的串(这一步可从后向前做个可行性dp即可),则dp(i, k)显然不可能成为最优答案的前缀。
然后会发现,当前可能的dp值,是某一个串s以及它的一些前缀,这样状态数就可以表示了。
再考虑转移,当前尝试往后拼接串t,等价于能转移到 s的一些前缀(不拼)或者 s的一些前缀 + t, 然后我们要求出字典序最小且之后有解且最长的s',以及它的可能前缀状态。
显然此时我们要支持快速比较串s的前缀、s的前缀+t的字典序大小,有两种做法,一种是哈希之后二分lcp然后比较下一位置,还有一种是Z函数快速查lcp、为O(1)。这里学了一下Z函数:
Z函数也称为扩展kmp,假设现在有一个串s和t,通过一个对t的O(n)时间预处理,之后可以线性时间对串s的每一个下标i,求以s[i]为开头能和串t匹配的最大长度。
具体做法是对t的每一个下标i求其能与t开头匹配的最大长度,求的时候利用一下之前已经求出的信息(类似manacher),匹配的时候同理。
Z函数模板题:
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
#define pll pair<ll, ll>
#define double long double
//#define int long long
using namespace std;
const int N = 1e6 + 100;
const int inf = 1e9;
//const ll inf = 1e18;
const ll mod = 998244353;//1e9 + 7;
#ifdef LOCAL
void debug_out(){cerr << endl;}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T)
{
cerr << " " << to_string(H);
debug_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
void sol()
{
string s, t;
cin >> t >> s;
int n, m;
n = s.length();
m = t.length();
VI Z(n, 0);
int lp, rp;
lp = rp = 0;
for(int i = 1; i < n; i++)
{
if(i <= rp && i + Z[i - lp] - 1 < rp)
Z[i] = Z[i - lp];
else
{
int k = max(0, rp - i + 1);
while(i + k < n && s[k] == s[i + k])
++k;
Z[i] = k;
}
if(i + Z[i] - 1 > rp)
lp = i, rp = i + Z[i] - 1;
}
Z[0] = n;
ll res = 0;
for(int i = 0; i < n; i++)
res ^= 1LL * (i + 1) * (Z[i] + 1);
cout << res << '\n';
res = 0;
lp = rp = -1;
VI ans(m, 0);
for(int i = 0; i < m; i++)
{
if(i <= rp && i + Z[i - lp] - 1 < rp)
ans[i] = Z[i - lp];
else
{
int k = max(0, rp - i + 1);
while(i + k < m && k < n && s[k] == t[i + k])
++k;
ans[i] = k;
}
if(i + ans[i] - 1 > rp)
lp = i, rp = i + ans[i] - 1;
res ^= 1LL * (i + 1) * (ans[i] + 1);
}
cout << res << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
// int tt;
// cin >> tt;
// while(tt--)
sol();
}
回到原题,分几类比较情况,需要用到Z函数的地方:比较s(1 ~ a) + t 和 s(1 ~ b) 字典序(b > a),则就是比较s(a + 1 ~ b) 与t,因此对t预处理Z数组即可。
转移过程可以枚举下一轮长度,维护一个字典序相同,长度逐渐增长的栈,然后应用上述比较即可。
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
#define pll pair<ll, ll>
#define double long double
//#define int long long
using namespace std;
const int N = 1e6 + 100;
const int inf = 1e9;
//const ll inf = 1e18;
const ll mod = 998244353;//1e9 + 7;
#ifdef LOCAL
void debug_out(){cerr << endl;}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T)
{
cerr << " " << to_string(H);
debug_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
void sol()
{
int n, m;
cin >> n >> m;
vector<string> a(n);
for(int i = 0; i < n; i++)
cin >> a[i];
vector<vector<bool>> can(n, vector<bool>(m + 1, 0));
for(int i = n - 1; i >= 0; i--)
{
if(i == n - 1)
{
can[i][m] = 1;
continue;
}
for(int j = 0; j <= m; j++)
{
if(can[i + 1][j] || (j + (int)a[i + 1].length() <= m && can[i + 1][j + a[i + 1].length()]))
can[i][j] = 1;
}
}
string s = "", t = "", ans = "";
VI Z, mat;
vector<pii> stk;
vector<bool> ed(m + 1, 0);
ed[0] = 1;
for(int i = 0; i < n; i++)
{
t = a[i];
stk.clear();
int ls, lt;
ls = s.length();
lt = t.length();
Z.resize(lt);
mat.resize(ls);
int lp, rp;
lp = rp = -1;
for(int j = 1; j < lt; j++)
{
if(j <= rp && j + Z[j - lp] - 1 < rp)
Z[j] = Z[j - lp];
else
{
int k = max(rp - j + 1, 0);
while(j + k < lt && t[k] == t[j + k])
++k;
Z[j] = k;
}
if(j + Z[j] - 1 > rp)
lp = j, rp = j + Z[j] - 1;
}
lp = rp = -1;
for(int j = 0; j < ls; j++)
{
if(j <= rp && j + Z[j - lp] - 1 < rp)
mat[j] = Z[j - lp];
else
{
int k = max(0, rp - j + 1);
while(k < lt && j + k < ls && t[k] == s[j + k])
++k;
mat[j] = k;
}
if(j + mat[j] - 1 > rp)
lp = j, rp = j + mat[j] - 1;
}
auto small = [&](int x)
{
int len = mat[x];
if(len == lt || x + len == ls)
return (bool)0;
return t[len] < s[x + len];
};
auto big = [&](int x)
{
int len = mat[x];
if(len == lt || x + len == ls)
return (bool)0;
return t[len] > s[x + len];
};
auto same = [&](int x)
{
int len = mat[x];
if(len == lt || x + len == ls)
return 1;
return 0;
};
auto cmp = [&](pii x, pii y)
{
if(y.sc == 0)
{
if(x.sc == 0 || x.fi >= y.fi)
return 0;
int len = mat[x.fi];
if(x.fi + len >= y.fi)
return 0;
if(small(x.fi))
return -1;
return 1;
}
if(x.sc == 0)
{
if(big(y.fi))
return -1;
if(same(y.fi))
return 0;
return 1;
}
int len = mat[y.fi];
if(y.fi + len > x.fi)
{
int ex = y.fi + len - x.fi;
int le = Z[lt - ex];
if(le == ex)
return 0;
if(t[le] < t[lt - ex + le])
return -1;
return 1;
}
else
{
if(big(y.fi))
return -1;
if(same(y.fi))
return 0;
return 1;
}
};
for(int j = 1; j <= m; j++)
{
if(!can[i][j])
continue;
if(ed[j] || (j - lt >= 0 && ed[j - lt]))
{
int op = -1;
if(ed[j] && j - lt >= 0 && ed[j - lt])
{
if(small(j - lt))
op = 1;
else op = 0;
}
else
{
if(ed[j])
op = 0;
else op = 1;
}
pii cur = pii(j - op * lt, op);
while(!stk.empty() && cmp(cur, stk.back()) < 0)
stk.pop_back();
if(stk.empty() || cmp(cur, stk.back()) == 0)
stk.pb(cur);
}
}
fill(all(ed), 0);
ed[0] = 1;
if(!stk.empty())
{
pii cur = stk.back();
s = s.substr(0, cur.fi);
if(cur.sc == 1)
s += t;
if(s.length() == m)
ans = s;
trav(v, stk)
{
int len = v.fi + v.sc * lt;
ed[len] = 1;
}
}
else
s = "";
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
// int tt;
// cin >> tt;
// while(tt--)
sol();
}