「十二省聯考 2019」字串問題
知識點:SA,可持久化線段樹,優化建圖,DAGDP
原題面:Loj,Luogu
神筆出題人居然卡清空/jk
調一上午發現把昨天的 TLE 程式碼的鄰接表換成 vector
就過了/jk
草草草草草
簡述
\(T\) 組資料,每次給定一字串 \(S\)。
在 \(S\) 中存在 \(n_a\) 個 A 類子串 \((la_i, ra_i)\),\(n_b\) 個 B 類子串 \((lb_i,rb_i)\)。且存在 \(m\) 組支配關係,支配關係 \((x,y)\) 表示第 \(x\) 個 A 類串支配第 \(y\) 個 B 類串。
要求構造一個目標串 \(T\),滿足:
- \(T\) 由若干 A 類串拼接而成。
- 對於分割中所有相鄰的串,後一個串存在一個被前一個串支配的字首。
求該目標串的最大長度,若目標串可以無限長輸出 \(-1\)。
\(1\le T\le 100\),\(n_a,n_b,|S|,m\le 2\times 10^5\)。
6S,1G。
分析
首先建立圖論模型,從每個 A 類子串向其支配的 B 串連邊,從每個 B 串向以它為字首的 A 串連邊。A 串節點的權值為其長度,B 串節點權值為 0。
在圖上 DP 求得最長路即為答案,若圖中存在環則無解。
第一類邊有 \(m\) 條,但第二類邊數可以達到 \(n_an_b\) 級別,考慮優化建圖。
對於某 A 串 \((la_i, ra_i)\)
對於限制一,考慮求得 \(S\) 的 SA,對 \(\operatorname{height}\) 建立 ST 表,可在 \(sa\) 上二分求得滿足 \(\operatorname{lcp}\ge rb_j-lb_j+1\) 的區間的左右邊界,滿足條件的 A 串一定都在這段區間內。第二類邊轉化為區間連邊問題。
此時不考慮限制二直接線段樹優化建圖,可以拿到 80 分的好成績。
限制二實際上限定了 B 連邊的物件的長度。
考慮將所有 A,B 串按長度遞減排序,按長度遞減列舉 A 串並依次加入可持久化線段樹。
對於每一個 B 串,先找到使得 A 串長度大於其長度的最晚的歷史版本,此時線段樹上的所有 A 串長度都大於其長度,再向這棵線段樹上的節點連邊。
時間複雜度 \(O((|S| + n_a + n_b)\log n)\),空間複雜度 \(O(m + (|S| + n_a + n_b)\log n)\),不需要刻意卡常就能穩過。
看見上面輕描淡寫的是不是覺得這題太傻逼了?以下是菜雞 Lb 在程式碼實現上的小問題。
邊數在極限資料下可以達到 \(10^7\) 級別,不注意空間大小和清空時的實現會被卡到 60。這個時空限制顯然就是給選手亂搞的,陣列往大了開就行。
線上段樹優化建圖中,實點會在建樹操作中就與虛點連好邊。本題的實點是代表 A,B 串的節點,在本題的可持久化線段樹優化中,實點與虛點的連邊發生在動態開點的插入過程中。
在新建節點時,需要將該節點連向上一個版本中對應位置的節點。
對於 A 串 \((la_i, ra_i)\),它應該被插入到線段樹中 \(rk_{la_i}\) 的位置,即葉節點 \(rk_{la_i}\) 與該實點相連。
\(\operatorname{height}_1\) 沒有意義。
注意二分時的初始值。
long long
函式傳參順序 是通過棧傳遞的,因此是從右向左的,下面這段程式碼會輸出 cba
:
int f(int a, int b, int c) {
return 0;
}
int main() {
return f(printf("a"),printf("b"),printf("c"));
return 0;
}
程式碼
//知識點:SA,可持久化線段樹,優化建圖,DAGDP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
struct Str {
int l, r, lth, id;
} subs[kN << 1];
int node_num, n, na, nb, m, into[kN <<5];
int e_num, head[kN << 5], v[50 * kN], ne[50 * kN];
LL val[kN << 5], f[kN << 5];
char s[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(LL &fir_, LL sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
bool cmp(Str fir_, Str sec_) {
if (fir_.lth != sec_.lth) return fir_.lth > sec_.lth;
return fir_.id < sec_.id;
}
void Add(int u_, int v_) {
v[++ e_num] = v_, ne[e_num] = head[u_], head[u_] = e_num;
into[v_] ++;
}
namespace ST {
int Minn[kN][21], Log2[kN];
void MakeST(int *a_) {
for (int i = 1; i <= n; ++ i) Minn[i][0] = a_[i];
for (int i = 2; i <= n; ++ i) Log2[i] = Log2[i >> 1] + 1;
for (int j = 1; j <= 20; ++ j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++ i) { //
Minn[i][j] = std::min(Minn[i][j - 1], Minn[i + (1 << (j - 1))][j - 1]);
}
}
}
int Query(int l_, int r_) {
int k = Log2[r_ - l_ + 1];
return std::min(Minn[l_][k], Minn[r_ - (1 << k) + 1][k]);
}
}
namespace SA {
int sa[kN], rk[kN << 1];
int oldrk[kN << 1], cnt[kN], id[kN];
int height[kN];
void SuffixSort() {
int rknum = std::max(n, 300);
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) cnt[rk[i] = s[i]] ++;
for (int i = 1; i <= rknum; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
for (int w = 1, p; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i]]];
for (int i = 1; i <= rknum; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i]]] --] = id[i];
for (int i = 1; i <= n; ++ i) oldrk[i] = rk[i];
rknum = 0;
for (int i = 1; i <= n; ++ i) {
rknum += (oldrk[sa[i]] == oldrk[sa[i - 1]] &&
oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) ^ 1;
rk[sa[i]] = rknum;
}
}
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) {
k = 0;
} else {
if (k) -- k;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
s[i + k] == s[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
int Lcp(int x_, int y_) {
if (x_ > y_) std::swap(x_, y_);
return ST::Query(x_ + 1, y_);
}
void Init() {
SuffixSort();
GetHeight();
ST::MakeST(SA::height);
}
}
namespace Hjt {
#define ls lson[now_]
#define rs rson[now_]
#define mid ((L_+R_)>>1)
int root[kN], lson[kN << 5], rson[kN << 5];
void Insert(int &now_, int pre_, int L_, int R_, int pos_, int id_) {
now_ = ++ node_num;
ls = lson[pre_], rs = rson[pre_];
if (pre_) Add(now_, pre_);
if (L_ == R_) {
Add(now_, id_);
return ;
}
if (pos_ <= mid) {
Insert(ls, lson[pre_], L_, mid, pos_, id_);
Add(now_, ls);
} else {
Insert(rs, rson[pre_], mid + 1, R_, pos_, id_);
Add(now_, rs);
}
}
void AddEdge(int now_, int L_, int R_, int l_, int r_, int id_) {
if (! now_) return ;
if (l_ <= L_ && R_ <= r_) {
Add(id_, now_);
return ;
}
if (l_ <= mid) AddEdge(ls, L_, mid, l_, r_, id_);
if (r_ > mid) AddEdge(rs, mid + 1, R_, l_, r_, id_);
}
#undef ls
#undef rs
#undef mid
}
void Init() {
e_num = 0;
for (int i = 0; i <= node_num; ++ i) {
head[i] = val[i] = into[i] = f[i] = 0;
}
scanf("%s", s + 1);
n = strlen(s + 1);
SA::Init();
na = read();
for (int i = 1; i <= na; ++ i) {
int l_ = read(), r_ = read();
subs[i] = (Str) {l_, r_, r_ - l_ + 1, i};
val[i] = subs[i].lth;
}
nb = read();
for (int i = 1; i <= nb; ++ i) {
int l_ = read(), r_ = read();
subs[na + i] = (Str) {l_, r_, r_ - l_ + 1, na + i};
}
m = read();
for (int i = 1; i <= m; ++ i) {
int u_ = read(), v_ = read();
Add(u_, v_ + na); //Add(read(), read()+na) 會倒著讀
}
node_num = na + nb;
}
bool Check(int x_, int y_, int lth_) {
return SA::Lcp(x_, y_) >= lth_;
}
void AddEdgeB(int id_, int now_) {
int pos = SA::rk[subs[id_].l], l_ = pos, r_ = pos; //l_,r_ 初始值
for (int l = 1, r = pos - 1; l <= r; ) {
int mid = (l + r) >> 1;
if (Check(mid, pos, subs[id_].lth)) {
r = mid - 1;
l_ = mid;
} else {
l = mid + 1;
}
}
for (int l = pos + 1, r = n; l <= r; ) {
int mid = (l + r) >> 1;
if (Check(pos, mid, subs[id_].lth)) {
l = mid + 1;
r_ = mid;
} else {
r = mid - 1;
}
}
Hjt::AddEdge(Hjt::root[now_], 1, n, l_, r_, subs[id_].id);
}
void Build() {
node_num = na + nb;
std::sort(subs + 1, subs + na + nb + 1, cmp);
for (int now = 0, i = 1; i <= na + nb; ++ i) {
if (subs[i].id > na) {
AddEdgeB(i, now);
continue;
}
++ now;
Hjt::Insert(Hjt::root[now], Hjt::root[now - 1], 1, n, SA::rk[subs[i].l],
subs[i].id);
}
}
void TopSort() {
std::queue <int> q;
for (int i = 1; i <= node_num; ++ i) {
if (!into[i]) {
f[i] = val[i];
q.push(i);
}
}
while (! q.empty()) {
int u_ = q.front(); q.pop();
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
Chkmax(f[v_], f[u_] + val[v_]);
-- into[v_];
if (!into[v_]) q.push(v_);
}
}
LL ans = 0;
for (int i = 1; i <= node_num; ++ i) {
Chkmax(ans, f[i]);
if (into[i]) {
printf("-1\n");
return ;
}
}
printf("%lld\n", ans);
}
//=============================================================
int main() {
int T = read();
while (T --) {
Init();
Build();
TopSort();
}
return 0;
}