P5284 [十二省联考2019]字符串问题
SA字典序排序二分 + 线段树优化建图 + 判环 + 拓扑DP最长路
省选前一天做十二省联考两天T2,当模拟省选,结果写了俩十分部分分还都爆零了(因为一个数组少开了个零,一个似乎特判那里有问题),省选爆炸也可能和这个有关系,今天补上。
首先转化成最长路模型还是比较容易的,至少省选前我就能想到。A -(支配)- > B, B -(前缀)- > A,然后 S -> A, A -> T,跑一个从 \(S\) 到 \(T\) 的最长路即可。如果有环就是-1(因为边权均非负且无零环)
其中第1,3,4种边比较好搞,点数 \(O(n)\),边数 \(O(m)\)。但是第 2 种边很麻烦,需要一个B点向所有包含B前缀的A点连边,最大边数能达到 \(O(n^2)\) 的级别。那么我们需要考虑优化建图。
子串问题想后缀系列。看起来 SAM 很不可做的样子,我们考虑 SA。如果一些 \(A\) 点包含 \(B\) 前缀,那么这些 \(A\) 点所在的原串后缀一定是一段连续的区间(这个可能有点tbl),但是存在 \(A\) 是 \(B\) 的子串的情况的话将会有一些“缺口”。不过我们发现如果那样的话 \(A\) 的字典序(不是原串后缀字典序)一定比 \(B\) 的小,那么我们干脆直接按子串的字典序将 \(A\) 排序,查询可以直接二分(lower_bound + half_find)。再加上线段树优化建图,我们就做完了。
注意一下,这里最长路直接拓扑DP即可,不用SPFA,因为环可以判掉,就直接成一DAG了。
思维难度省选-,代码难度NOI
上6.5K代码的部分代码:
inline void Build()
inline int get_lcp(int l, int r) //rank l ~ rank r
int na, nb;
struct Substring {
int l, r, id;
inline void Read(int idd) {
read(l), read(r); id = idd;
}
bool operator <(const Substring a) const {
int lcp = get_lcp(rk[l], rk[a.l]);
int len1 = r - l + 1, len2 = a.r - a.l + 1;
if (lcp < len1 && lcp < len2) return s[l + lcp] < s[a.l + lcp];//Attention!!! : <
// if (lcp >= len1 && lcp >= len2) return ;//Attention!!! : > //Attention!!!!
return len1 < len2;
}
}A[N], B[N];
int mp[N], pos[N];
int ls[NN], rs[NN], root, ttot;
void build(int L, int R, int &cur)
void AddEdge(int L, int R, int l, int r, int from, int cur)
int dfn[NN + N], low[NN + N], dcnt;
int stk[NN + N], stop, col[NN + N], ctot;
bool instk[NN + N];
void tarjan(int cur)
int S, t;
int que[NN + N], front, rear;
ll dis[NN + N];
inline void get_ans() {
memset(dis, 0xc0, sizeof(dis));
dis[S] = 0;
for (register int i = 1; i <= t; ++i) if (!d[i]) que[++rear] = i;//Attention!!!!
while (front < rear) {
int cur = que[++front];
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to;
MAX(dis[to], dis[cur] + e[i].val);
--d[to];
if (!d[to]) que[++rear] = to;
}
}
printf("%lld\n", dis[t]);
}
inline int Find(int st, int l, int len) {//Attention!!!!!
int r = na, res = l - 1;
while (l <= r) {
int mid = (l + r) >> 1;
int lcp = get_lcp(rk[A[mid].l], rk[B[st].l]);//Attention!!!!!!
if (lcp >= len) res = mid, l = mid + 1;
else r = mid - 1;
}
return res;
}
inline void work() {
scanf("%s", s + 1); slen = strlen(s + 1);
Build();
read(na);
for (register int i = 1; i <= na; ++i) A[i].Read(i);
read(nb);
for (register int i = 1; i <= nb; ++i) B[i].Read(i);
sort(A + 1, A + 1 + na);
for (register int i = 1; i <= na; ++i) mp[A[i].id] = i;
build(1, na, root);
int m; read(m);
for (register int i = 1; i <= m; ++i) {
int a, b; read(a), read(b); a = mp[a];
addedge(pos[a], ttot + b, A[a].r - A[a].l + 1);
}
for (register int i = 1; i <= nb; ++i) {
int l = lower_bound(A + 1, A + 1 + na, B[i]) - A;
int r = Find(i, l, B[i].r - B[i].l + 1);//Attention!!!!!!
if (l > r) continue;
AddEdge(1, na, l, r, i + ttot, root);
}
S = ttot + nb + 1, t = S + 1;
for (register int i = 1; i <= na; ++i) {
addedge(S, pos[i], 0);
addedge(pos[i], t, A[i].r - A[i].l + 1);
}
for (register int i = 1; i <= t; ++i) {
if (!dfn[i]) tarjan(i);
}
if (ctot != t) { puts("-1"); return ; }
get_ans();
}