P5284 [十二省联考2019]字符串问题
这是一道涵盖了字符串、图论、数据结构三个方面的综合大题。
把这道题放在D1T2的人应该拖出去打
前置芝士
首先,您至少要会topsort。
其次,如果您只想拿个暴力分,字符串Hash就足够了;如果您想拿满分,SA和SAM您至少要会一种(本文采用SA)。
最后,正解还需要您了解线段树优化建边,并在此基础上用主席树实现。
暴力算法一
对于测试点1~4,暴力建图,Hash优化,可以拿到40分的高分。
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 2e5 + 6;
char s[N];
int n, na, la[N], ra[N], nb, lb[N], rb[N], nm, ma[N], mb[N];
namespace Hash {
const int P = 13331;
ull h[N], p[N];
inline void main() {
p[0] = 1;
for (int i = 1; i <= n; i++) h[i] = h[i-1] * P + s[i], p[i] = p[i-1] * P;
}
inline ull get(int l, int r) {
return h[r] - h[l-1] * p[r-l+1];
}
}
namespace Graph {
vector<int> e[N<<1];
int a[N<<1], d[N<<1], f[N<<1];
queue<int> q;
inline void init() {
for (int i = 1; i <= na + nb; i++) e[i].clear(), a[i] = d[i] = f[i] = 0;
for (int i = 1; i <= na; i++) a[i] = ra[i] - la[i] + 1;
}
inline void add(int x, int y) {
e[x].push_back(y), ++d[y];
}
inline void topsort() {
for (int i = 1; i <= na + nb; i++) if (!d[i]) q.push(i);
while (q.size()) {
int x = q.front();
q.pop();
for (unsigned int i = 0; i < e[x].size(); i++) {
int y = e[x][i];
f[y] = max(f[y], f[x] + a[x]);
if (!--d[y]) q.push(y);
}
}
for (int i = 1; i <= na + nb; i++)
if (d[i]) {
puts("-1");
return;
}
ll ans = 0;
for (int i = 1; i <= na + nb; i++) ans = max(ans, (ll)f[i] + a[i]);
cout << ans << endl;
}
inline void main() {
Hash::main();
init();
for (int i = 1; i <= nm; i++) add(ma[i], mb[i] + na);
for (int j = 1; j <= nb; j++) {
ull now = Hash::get(lb[j], rb[j]);
int len = rb[j] - lb[j];
for (int i = 1; i <= na; i++)
if (len < a[i] && Hash::get(la[i], la[i] + len) == now) add(j + na, i);
}
topsort();
}
}
inline void work() {
scanf("%s", s + 1);
n = strlen(s + 1);
scanf("%d", &na);
for (int i = 1; i <= na; i++) scanf("%d %d", &la[i], &ra[i]);
scanf("%d", &nb);
for (int i = 1; i <= nb; i++) scanf("%d %d", &lb[i], &rb[i]);
scanf("%d", &nm);
for (int i = 1; i <= nm; i++) scanf("%d %d", &ma[i], &mb[i]);
Graph::main();
}
int main() {
int T;
cin >> T;
while (T--) work();
return 0;
}
暴力算法二
对于测试点1、4、5、6,所有A串的前缀总数是可接受的,全部枚举出来,暴力建图,可以拿到40分的高分。
暴力算法三
结合暴力算法一和二,面向数据分治,可以拿到60分的高分。
(滑稽
错误算法
我们对字符串跑一遍SA,一个显而易见的事实是,每个 \(b\) 串一定会连向SA上的一个区间。
线段树优化建图即可。
虽然是错误算法,但可以拿到80分的高分。
正确算法
错误算法中有这样一句话:
每个 \(b\) 串一定会连向SA上的一个区间。
但是,显然,如果 \(b\) 串长度大于 \(a\) 串,那么 \(b\) 串一定不会是 \(a\) 串的前缀。
因此,每个 \(b\) 串一定会连向SA上的一个区间中的若干个点。
考虑用主席树代替线段树。
按长度从大到小将 \(a\) 串依次插入主席树中。
在大于 \(b\) 串长度的主席树历史版本上优化建图。
下面代码的实现细节参考了小粉兔在https://www.cnblogs.com/PinkRabbit/p/SHOI2019D1T2.html中的思路,但并不雷同
#include <bits/stdc++.h>
#define ll long long
#define mid ((l + r) >> 1)
using namespace std;
const int N = 2e5 + 6;
char s[N];
int T, n, na, la[N], ra[N], nb, lb[N], rb[N], nm, ma[N], mb[N], tot;
struct Str {
int l, len, id;
inline Str() {}
inline Str(int l, int len, int id) : l(l), len(len), id(id) {}
inline bool operator < (const Str o) const {
return (len ^ o.len) ? len > o.len : id < o.id;
}
} str[N<<1];
namespace SA {
int m = 26, sa[N], rk[N], tp[N], tx[N], he[N], st[N][20];
inline void tsort() {
for (int i = 1; i <= m; i++) tx[i] = 0;
for (int i = 1; i <= n; i++) ++tx[rk[i]];
for (int i = 1; i <= m; i++) tx[i] += tx[i-1];
for (int i = n; i; i--) sa[tx[rk[tp[i]]]--] = tp[i];
}
inline bool pd(int i, int w) {
return tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w];
}
inline void main() {
for (int i = 1; i <= n; i++) rk[i] = s[i] - 'a' + 1, tp[i] = i;
tsort();
for (int w = 1, p = 0; p < n; w <<= 1, m = p) {
p = 0;
for (int i = 1; i <= w; i++) tp[++p] = n - w + i;
for (int i = 1; i <= n; i++) if (sa[i] > w) tp[++p] = sa[i] - w;
tsort(), swap(rk, tp), rk[sa[1]] = p = 1;
for (int i = 2; i <= n; i++) rk[sa[i]] = pd(i, w) ? p : ++p;
}
int p = 0;
for (int i = 1; i <= n; i++) {
if (p) --p;
int j = sa[rk[i]-1];
while (s[i+p] == s[j+p]) ++p;
he[rk[i]] = p;
}
for (int i = 1; i <= n; i++) st[i][0] = he[i];
int w = log(n) / log(2);
for (int k = 1; k <= w; k++)
for (int i = 1; i + (1 << k) - 1 <= n; i++)
st[i][k] = min(st[i][k-1], st[i+(1<<(k-1))][k-1]);
}
inline int get(int l, int r) {
int k = log(r - l + 1) / log(2);
return min(st[l][k], st[r-(1<<k)+1][k]);
}
}
namespace Graph {
vector<int> e[N<<5];
ll a[N<<5], d[N<<5], f[N<<5];
queue<int> q;
inline void add(int x, int y) {
e[x].push_back(y), ++d[y];
}
inline void topsort() {
for (int i = 1; i <= tot; i++) if (!d[i]) q.push(i);
while (q.size()) {
int x = q.front();
q.pop();
for (unsigned int i = 0; i < e[x].size(); i++) {
int y = e[x][i];
f[y] = max(f[y], f[x] + a[x]);
if (!--d[y]) q.push(y);
}
}
for (int i = 1; i <= tot; i++)
if (d[i]) {
puts("-1");
return;
}
ll ans = 0;
for (int i = 1; i <= tot; i++) ans = max(ans, f[i] + a[i]);
printf("%lld\n", ans);
}
}
namespace Seg {
struct T {
int l, r;
} t[N<<5];
int rt[N];
int ins(int o, int l, int r, int x, int k) {
int p = ++tot;
t[p] = t[o];
if (o) Graph::add(p, o);
if (l == r) Graph::add(p, k);
else if (x <= mid) Graph::add(p, t[p].l = ins(t[o].l, l, mid, x, k));
else Graph::add(p, t[p].r = ins(t[o].r, mid + 1, r, x, k));
return p;
}
void add(int p, int l, int r, int L, int R, int k) {
if (!p || r < L || l > R) return;
if (L <= l && r <= R) Graph::add(k, p);
else add(t[p].l, l, mid, L, R, k), add(t[p].r, mid + 1, r, L, R, k);
}
}
inline void work() {
scanf("%s", s + 1);
n = strlen(s + 1);
SA::main();
scanf("%d", &na);
for (int i = 1; i <= na; i++) scanf("%d %d", &la[i], &ra[i]);
scanf("%d", &nb);
for (int i = 1; i <= nb; i++) scanf("%d %d", &lb[i], &rb[i]);
scanf("%d", &nm);
for (int i = 1; i <= nm; i++) scanf("%d %d", &ma[i], &mb[i]);
for (int i = 1; i <= na; i++) Graph::a[i] = ra[i] - la[i] + 1;
for (int i = 1; i <= na; i++) str[i] = Str(la[i], ra[i] - la[i] + 1, i);
for (int i = 1; i <= nb; i++) str[na+i] = Str(lb[i], rb[i] - lb[i] + 1, na + i);
sort(str + 1, str + na + nb + 1);
tot = na + nb;
int now = 0;
for (int i = 1; i <= na + nb; i++)
if (str[i].id <= na) ++now, Seg::rt[now] = Seg::ins(Seg::rt[now-1], 1, n, SA::rk[str[i].l], str[i].id);
else {
int k = SA::rk[str[i].l], l = 1, r = k, L, R;
while (l < r)
if (SA::get(mid + 1, k) >= str[i].len) r = mid;
else l = mid + 1;
L = l, l = k + 1, r = n + 1;
while (l < r)
if (SA::get(k + 1, mid) >= str[i].len) l = mid + 1;
else r = mid;
Seg::add(Seg::rt[now], 1, n, L, R = l - 1, str[i].id);
}
for (int i = 1; i <= nm; i++) Graph::add(ma[i], mb[i] + na);
Graph::topsort();
for (int i = 1; i <= tot; i++) Graph::e[i].clear(), Graph::a[i] = Graph::d[i] = Graph::f[i] = 0;
}
int main() {
cin >> T;
while (T--) work();
return 0;
}
P5284 [十二省联考2019]字符串问题的更多相关文章
- Luogu P5284 [十二省联考2019]字符串问题
好难写的字符串+数据结构问题,写+调了一下午的说 首先理解题意后我们对问题进行转化,对于每个字符串我们用一个点来代表它们,其中\(A\)类串的点权为它们的长度,\(B\)类串的权值为\(0\) 这样我 ...
- 洛谷P5284 [十二省联考2019]字符串问题 [后缀树]
传送门 思路 设\(dp_i\)表示以\(i\)结尾的\(A\)串,能达到的最长长度. 然后发现这显然可以\(i\)往自己控制的\(k\)连边,\(k\)往能匹配的\(j\)连边,就是个最长路,只要建 ...
- 【题解】Luogu P5284 [十二省联考2019]字符串问题
原题传送门 我用sa做的本题 (码量似乎有点大) 先对原串建sa 考虑如何建图: 从大到小枚举长度len 先将height中等于len的两个位置在并查集合并起来,将lst也合并(lst是链表) 再将长 ...
- 洛谷P5284 [十二省联考2019]字符串问题(SAM+倍增+最长路)
题面 传送门 题解 首先,我们把串反过来,那么前缀就变成后缀,建一个\(SAM\).我们发现一个节点的后缀是它的所有祖先 那么我们是不是直接按着\(parent\)树建边就可以了呢? 显然不是.我们假 ...
- [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增
题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...
- 【BZOJ5496】[十二省联考2019]字符串问题(后缀树)
[BZOJ5496][十二省联考2019]字符串问题(后缀树) 题面 BZOJ 洛谷 题解 首先显然可以把具有支配关系的串从\(A\)到\(B\)连一条有向边,如果\(B_i\)是\(A_j\)的前缀 ...
- [LOJ3049] [十二省联考 2019] 字符串问题
题目链接 LOJ:https://loj.ac/problem/3049 洛谷:https://www.luogu.org/problemnew/show/P5284 BZOJ:https://www ...
- LOJ3049 [十二省联考2019] 字符串问题 【后缀自动机】【倍增】【拓扑排序】
题目分析: 建出后缀自动机,然后把A串用倍增定位到后缀自动机上,再把B串用倍增定位到后缀自动机上. SAM上每个点上的A串根据长度从小到大排序,建点,依次连边. 再对于SAM上面每个点,连到儿子的边, ...
- 洛谷.5284.[十二省联考2019]字符串问题(后缀自动机 拓扑 DP)
LOJ BZOJ 洛谷 对这题无话可说,确实比较...裸... 像dls说的拿拓扑和parent树一套就能出出来了... 另外表示BZOJ Rank1 tql... 暴力的话,由每个\(A_i\)向它 ...
随机推荐
- SFP光模块与SFP+、XFP、QSFP、GBIC、BIDI的区别
SFP.SFP+.XFP.QSFP.GBIC和BIDI等不同封装类型光模块不断推陈出新,我们就以市场上比较常见的为主,来谈谈它与其他类似光模块的区别. SFP光模块 SFP光模块又称⼩封装可插拔光模块 ...
- Vue 路由心得总结
一. 嵌套路由 a.主页面, main.vue , 子页面分别为 shouye.vue / liuyan.vue / about.vue , 首先, 在main.vue加入导 ...
- ubuntu下安装Visual Studio Code
环境准备 先安装一般umake没有问题 sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make sudo apt-get update sudo ...
- 关于配置ssh免密码登录后,仍提示输入密码
一.在A端创建密钥对: [root@A ~] -P '' 二.如果B机器没有.ssh和authorized_keys文件则创建这个文件夹和文件先,创建后要chown改成当前用户的所属者,其次也要改: ...
- LeetCode21—合并两个有序链表
方法一:这是我一开始的想法,将链表L2的各个元素与链表L1的元素进行逐一比较,将L2中的数据元素插入L1中的合适位置. 时间复杂度:O(m+n):空间复杂度:O(1) 1)首先,可能要对第一个元素进行 ...
- WebSocket原理
一 . WebSocket原理 1.1.背景 WebSocket 是基于Http 协议的改进,Http 为无状态协议,基于短连接,需要频繁的发起请求,第二 Http 只能客户端发起请求,服务端无法主动 ...
- Spring Boot(二):数据库操作
本文主要讲解如何通过spring boot来访问数据库,本文会演示三种方式来访问数据库,第一种是JdbcTemplate,第二种是JPA,第三种是Mybatis.之前已经提到过,本系列会以一个博客系统 ...
- 微信小程序音乐播放
最近在写一个艾美食艾音乐的微信小程序,其中有用到音乐播放的功能,基本播放切换功能已经实现,但是在反复切换歌曲.重新进入歌曲以及单曲循环.列表循环的测试过程中还是发生了bug,特此写一篇文章,捋一下思路 ...
- codeforces510D
Fox And Jumping CodeForces - 510D Fox Ciel is playing a game. In this game there is an infinite long ...
- 「FFT」题单(upd 2019.4.28)
持续更新(last upd 2019.4.28) ZJOI2014 力 [题目链接] 解法 对原式进行转换,然后卷积FFT套上去求解就可以了. 推导过程简洁版: \[F_i=\sum_{j<i} ...