LOJ 3049: 洛谷 P5284: 「十二省联考 2019」字符串问题
题目传送门:LOJ #3049。
题意简述:
给定一个长度为 \(n\) 的母串 \(S\)。
有 \(n_a\) 个 A 类串,都是 \(S\) 的子串,以区间的形式给出。
有 \(n_b\) 个 B 类串,都是 \(S\) 的子串,以区间的形式给出。
有 \(m\) 个支配关系,形式为第 \(i\) 个 A 类串支配第 \(j\) 个 B 类串。
你需要求出最长的字符串 \(T\) 的长度,使得 \(T\) 可以被划分为若干个 A 类串的拼接,并且相邻两个 A 类串 \(t_i\) 和 \(t_{i+1}\) 满足 \(t_i\) 支配某个 B 类串 \(j\),而 \(j\) 是 \(t_{i+1}\) 的前缀。
如果 \(T\) 可以无限长,输出 -1。
题解:
如果我们设法从每个 B 类串向存在前缀为这个 B 类串的 A 类串连边,则显然若有环则答案可以无限大,若无环则可以做一遍 DAG DP 得到答案。
然而我们并不能直接实现这一过程,且不论能否快速确定前缀从属关系,这类边的条数就能达到 \(\mathcal{O}(n_an_b)\) 级别。
当边数太多时,首先应该想到的就是数据结构优化建边,典型的例子是树状结构优化建边,例如线段树和后缀树。
实际上这题可以直接使用后缀树优化建边,边数只有线性级别,但是过程还是 \(\mathcal{O}(n\log n)\) 的。至于建后缀树可以直接建也可以对反串建 SAM。
对于我这种不会后缀树,SAM 理解也不深的菜兔,还是选择了后缀数组。
首先建出后缀数组,这个大家都会。然后对每个 B 类串考虑如何连边。
假设我们已经对每个 A 类和 B 类串在母串上定位了,那么包含某个 B 类串作为前缀的 A 类串需要满足两个条件:
该 A 串的左端点对应的后缀和 B 串的左端点对应的后缀的 LCP 长度应该大于 B 串的长度,并且该 A 串的长度也应该大于 B 串的长度。
第一个条件可以转化为对应着后缀数组上的一段区间,具体根据 Height 数组确定,这一步建出 ST 表后二分可以在 \(\mathcal{O}(\log n)\) 的时间内解决。
第二个条件似乎没有什么巧妙的办法。不过我们可以考虑按照长度从大到小的顺序加入每个串,这样就能够扔掉第二个限制了。
所以具体的思路就是按照长度从大到小排序插入 A 类串,在 B 类串和数据结构之间建边,使用合适的数据结构维护历史版本和区间。
很显然,主席树便是合适的数据结构,主席树优化建边,树内边数 \(\mathcal{O}(n_a\log n)\),树外边数 \(\mathcal{O}(n_b\log n)\)。
接下来是代码,复杂度 \(\mathcal{O}((n+n_a+n_b)\log n)\):
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int MN = 200005;
const int MS = 4200005;
char str[MN];
int N, Sig, rk[MN], SA[MN], SA2[MN], buk[MN], tmp[MN];
int Height[MN];
inline void getHeight() {
for (int i = 1, k = 0; i <= N; ++i) {
if (rk[i] == 1) { Height[rk[i]] = k = 0; continue; }
if (k) --k;
int j = SA[rk[i] - 1];
while (i + k <= N && j + k <= N && str[i + k] == str[j + k]) ++k;
Height[rk[i]] = k;
}
}
inline void RSort() {
for (int i = 1; i <= Sig; ++i) buk[i] = 0;
for (int i = 1; i <= N; ++i) ++buk[rk[i]];
for (int i = 1; i <= Sig; ++i) buk[i] += buk[i - 1];
for (int i = N; i >= 1; --i) SA[buk[rk[SA2[i]]]--] = SA2[i];
}
inline void getSA(char *str) {
Sig = 127, rk[N + 1] = 0;
for (int i = 1; i <= N; ++i) rk[i] = str[i], SA2[i] = i;
RSort();
for (int j = 1; j <= N; j <<= 1) {
int p = 0;
for (int i = N - j + 1; i <= N; ++i) SA2[++p] = i;
for (int i = 1; i <= N; ++i) if (SA[i] > j) SA2[++p] = SA[i] - j;
RSort();
tmp[SA[1]] = p = 1;
for (int i = 2; i <= N; ++i) {
int lst = SA[i - 1], now = SA[i];
if (rk[lst] != rk[now] || rk[lst + j] != rk[now + j]) ++p;
tmp[SA[i]] = p;
}
for (int i = 1; i <= N; ++i) rk[i] = tmp[i];
if ((Sig = p) == N) break;
}
getHeight();
}
int Lg[MN];
inline void Log(int N) {
Lg[0] = -1;
for (int i = 1; i <= N; ++i) Lg[i] = Lg[i >> 1] + 1;
}
int ST[MN][18];
inline void InitST() {
for (int i = 2; i <= N; ++i) ST[i][0] = Height[i];
for (int j = 1; j <= Lg[N - 1]; ++j) {
for (int i = 1; i <= 1 << j; ++i) ST[i][j] = 0;
for (int i = 1 << j | 1; i <= N; ++i)
ST[i][j] = std::min(ST[i - (1 << (j - 1))][j - 1], ST[i][j - 1]);
}
}
int NA, la[MN], ra[MN];
int NB, lb[MN], rb[MN];
struct sub {
int lb, len, typ, id;
sub() {}
sub(int lb, int len, int typ, int id) : lb(lb), len(len), typ(typ), id(id) {}
inline friend bool operator <(sub i, sub j) {
return i.len == j.len ? i.typ < j.typ : i.len > j.len;
}
} substrs[MN * 2];
int d[MS];
std::vector<int> G[MS];
inline void addEdge(int x, int y) { ++d[y]; G[x].push_back(y); }
int rt[MN], lc[MS], rc[MS], wgh[MS], cnt;
void Mdf(int &rt, int l, int r, int p, int x) {
lc[++cnt] = lc[rt], rc[cnt] = rc[rt];
if (rt) addEdge(cnt, rt);
wgh[rt = cnt] = 0;
if (l == r) { addEdge(rt, x); return ; }
int mid = (l + r) >> 1;
if (p <= mid) Mdf(lc[rt], l, mid, p, x), addEdge(rt, lc[rt]);
else Mdf(rc[rt], mid + 1, r, p, x), addEdge(rt, rc[rt]);
}
void Edg(int rt, int l, int r, int a, int b, int x) {
if (!rt || r < a || b < l) return ;
if (a <= l && r <= b) { addEdge(x, rt); return ; }
int mid = (l + r) >> 1;
Edg(lc[rt], l, mid, a, b, x);
Edg(rc[rt], mid + 1, r, a, b, x);
}
int que[MS], l, r;
LL f[MS];
int main() {
int T; scanf("%d", &T);
Log(200000);
while (T--) {
scanf("%s", str + 1);
N = strlen(str + 1);
getSA(str);
InitST();
scanf("%d", &NA);
for (int i = 1; i <= NA; ++i)
scanf("%d%d", &la[i], &ra[i]),
substrs[i] = sub(rk[la[i]], ra[i] - la[i] + 1, 0, i);
scanf("%d", &NB);
for (int i = 1; i <= NB; ++i)
scanf("%d%d", &lb[i], &rb[i]),
substrs[NA + i] = sub(rk[lb[i]], rb[i] - lb[i] + 1, 1, i);
std::sort(substrs + 1, substrs + NA + NB + 1);
cnt = NA + NB;
for (int i = 1; i <= NA; ++i) wgh[i] = ra[i] - la[i] + 1;
for (int i = 1; i <= NB; ++i) wgh[NA + i] = 0;
for (int i = 1, gen = 0; i <= NA + NB; ++i) {
sub p = substrs[i];
if (!p.typ) ++gen, Mdf(rt[gen] = rt[gen - 1], 1, N, p.lb, p.id);
else {
int Lb = p.lb, Rb = p.lb;
for (int j = Lg[p.lb - 1]; ~j; --j)
if (ST[Lb][j] >= p.len) Lb -= 1 << j;
for (int j = Lg[N - p.lb]; ~j; --j)
if (Rb + (1 << j) <= N && ST[Rb + (1 << j)][j] >= p.len) Rb += 1 << j;
Edg(rt[gen], 1, N, Lb, Rb, NA + p.id);
}
}
int M; scanf("%d", &M);
for (int i, j; M--; ) {
scanf("%d%d", &i, &j);
addEdge(i, NA + j);
}
LL Ans = 0;
l = 1, r = 0;
for (int i = 1; i <= cnt; ++i) {
f[i] = wgh[i];
if (!d[i]) que[++r] = i;
}
while (l <= r) {
int u = que[l++];
Ans = std::max(Ans, f[u]);
for (auto v : G[u]) {
f[v] = std::max(f[v], f[u] + wgh[v]);
if (!--d[v]) que[++r] = v;
}
}
if (r != cnt) puts("-1");
else printf("%lld\n", Ans);
for (int i = 1; i <= cnt; ++i) d[i] = 0, G[i].clear();
}
return 0;
}
实际上,同样是后缀数组预处理,关于建边有更好的方法,比如用后缀数组建出后缀树,然后在后缀树上乱搞建边,这样是线性的。
LOJ 3049: 洛谷 P5284: 「十二省联考 2019」字符串问题的更多相关文章
- LOJ #3049. 「十二省联考 2019」字符串问题
LOJ #3049. 「十二省联考 2019」字符串问题 https://loj.ac/problem/3049 题意:给你\(na\)个\(A\)类串,\(nb\)个\(B\)类串,\(m\)组支配 ...
- 「十二省联考 2019」字符串问题——SAM+DAG
题目 [题目描述] Yazid 和 Tiffany 喜欢字符串问题.在这里,我们将给你介绍一些关于字符串的基本概念. 对于一个字符串 $S$, 我们定义 $\lvert S\rvert$ 表示 $S$ ...
- 【LOJ 3049】「十二省联考 2019」字符串问题
这个D1T2绝对有毒... 首先我们构造一把反串的后缀自动机. 然后我们就需要找到每一个子串在SAM上的节点. 这个可以通过扫描线+树上倍增处理. 首先我们把所有的子串按照左端点排序, 然后从右往左扫 ...
- 「ZJOI2019」&「十二省联考 2019」题解索引
「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...
- 【LOJ】#3051. 「十二省联考 2019」皮配
LOJ#3051. 「十二省联考 2019」皮配 当时我在考场上觉得这题很不可做... 当然,出了考场后再做,我还是没发现学校和城市是可以分开的,导致我还是不会 事实上,若一个城市投靠了某个阵营,学校 ...
- 「十二省联考 2019」皮配——dp
题目 [题目描述] #### 题目背景一年一度的综艺节目<中国好码农>又开始了.本季度,好码农由 Yazid.Zayid.小 R.大 R 四位梦想导师坐镇,他们都将组建自己的梦想战队,并率 ...
- 「洛谷5290」「LOJ3052」「十二省联考 2019」春节十二响【启发式合并】
题目链接 [洛谷传送门] [LOJ传送门] 题目大意 给定一棵树,每次选取树上的一个点集,要求点集中的每个点不能是另一个点的祖先,选出点集的代价为点集中权值最大点的权值,问将所有点都选一遍的最小代价为 ...
- 「洛谷5283」「LOJ3048」「十二省联考2019」异或粽子【可持久化01trie+优先队列】
题目链接 [洛谷传送门] [LOJ传送门] 题目大意 让你求区间异或和前\(k\)大的异或和的和. 正解 这道题目是Blue sky大佬教我做的(祝贺bluesky大佬进HA省A队) 我们做过某一些题 ...
- LOJ#3048. 「十二省联考 2019」异或粽子 Trie
原文链接www.cnblogs.com/zhouzhendong/p/LOJ3048.html 题解 $O(n\log^2 {a_i})$ 的做法比较简单: 1. 求出第 k 大的是什么: 二分答案, ...
随机推荐
- DeconvNet 论文阅读理解
学习语义分割反卷积网络DeconvNet 一点想法:反卷积网络就是基于FCN改进了上采样层,用到了反池化和反卷积操作,参数量2亿多,非常大,segnet把两个全连接层去掉,效果也能很好,显著减少了参数 ...
- EF Core 多对多配置
1.配置2个数据表 T_Authors ,T_Books 2.新建控制台项目,安装EF驱动 PM> Install-Package Pomelo.EntityFrameworkCore.Mysq ...
- vuex的使用步骤
第一步: 安装vuex:npm install vuex --save 第二步:在src下创建文件夹store及文件index.js import Vue from 'vue'; import Vue ...
- Spring+SpringMVC+Hibernate小案例(实现Spring对Hibernate的事务管理)
原文地址:https://blog.csdn.net/jiegegeaa1/article/details/81975286 一.工作环境 编辑器用的是MyEclipse,用Mysql数据库,mave ...
- Spring Security 无法登陆,报错:There is no PasswordEncoder mapped for the id “null”
编写好继承了WebSecurityConfigurerAdapter类的WebSecurityConfig类后,我们需要在configure(AuthenticationManagerBuilder ...
- Linux(Ubuntu)使用日记(三)------git安装使用
1. 安装 首先,确认你的系统是否已安装git,可以通过git指令进行查看,如果没有,在命令行模式下输入sudo apt-get install git命令进行安装. 2. 配置 git confi ...
- Kali Linux 发布 2019.1 版
Kali Linux 是一个基于 Debian 的发行版,关注于高级渗透测试和安全审计并搭载各种常用工具,由 Offensive Security 维护.后者是一个提供信息安全训练的公司. 该项目于日 ...
- centos7.5误删python2.7之后,导致yum和Pythonm命令无法使用
问题描述 最近想要将服务器上的Python2.7升级成3.x的版本时.使用了如下命令: (1)强制删除已安装python及其关联 # rpm -qa|grep python|xargs rpm -ev ...
- Kubernetes 中的渐进式交付:蓝绿部署和金丝雀部署
渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚. 这里有一些有趣的项目,使得渐进式交付在 Ku ...
- 栈&队列
队列部分 普通队列 举个形象的例子:排队买票. 有一列人在排队买票,前面来的人买完票就离开,后面来的人需要站在最后--依次类推. 在计算机中,数据结构队列有一个头指针和尾指针,头指针加一就代表有一个数 ...