这是一道涵盖了字符串图论数据结构三个方面的综合大题。

把这道题放在D1T2的人应该拖出去打

前置芝士

首先,您至少要会topsort

其次,如果您只想拿个暴力分,字符串Hash就足够了;如果您想拿满分,SASAM您至少要会一种(本文采用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]字符串问题的更多相关文章

  1. Luogu P5284 [十二省联考2019]字符串问题

    好难写的字符串+数据结构问题,写+调了一下午的说 首先理解题意后我们对问题进行转化,对于每个字符串我们用一个点来代表它们,其中\(A\)类串的点权为它们的长度,\(B\)类串的权值为\(0\) 这样我 ...

  2. 洛谷P5284 [十二省联考2019]字符串问题 [后缀树]

    传送门 思路 设\(dp_i\)表示以\(i\)结尾的\(A\)串,能达到的最长长度. 然后发现这显然可以\(i\)往自己控制的\(k\)连边,\(k\)往能匹配的\(j\)连边,就是个最长路,只要建 ...

  3. 【题解】Luogu P5284 [十二省联考2019]字符串问题

    原题传送门 我用sa做的本题 (码量似乎有点大) 先对原串建sa 考虑如何建图: 从大到小枚举长度len 先将height中等于len的两个位置在并查集合并起来,将lst也合并(lst是链表) 再将长 ...

  4. 洛谷P5284 [十二省联考2019]字符串问题(SAM+倍增+最长路)

    题面 传送门 题解 首先,我们把串反过来,那么前缀就变成后缀,建一个\(SAM\).我们发现一个节点的后缀是它的所有祖先 那么我们是不是直接按着\(parent\)树建边就可以了呢? 显然不是.我们假 ...

  5. [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增

    题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...

  6. 【BZOJ5496】[十二省联考2019]字符串问题(后缀树)

    [BZOJ5496][十二省联考2019]字符串问题(后缀树) 题面 BZOJ 洛谷 题解 首先显然可以把具有支配关系的串从\(A\)到\(B\)连一条有向边,如果\(B_i\)是\(A_j\)的前缀 ...

  7. [LOJ3049] [十二省联考 2019] 字符串问题

    题目链接 LOJ:https://loj.ac/problem/3049 洛谷:https://www.luogu.org/problemnew/show/P5284 BZOJ:https://www ...

  8. LOJ3049 [十二省联考2019] 字符串问题 【后缀自动机】【倍增】【拓扑排序】

    题目分析: 建出后缀自动机,然后把A串用倍增定位到后缀自动机上,再把B串用倍增定位到后缀自动机上. SAM上每个点上的A串根据长度从小到大排序,建点,依次连边. 再对于SAM上面每个点,连到儿子的边, ...

  9. 洛谷.5284.[十二省联考2019]字符串问题(后缀自动机 拓扑 DP)

    LOJ BZOJ 洛谷 对这题无话可说,确实比较...裸... 像dls说的拿拓扑和parent树一套就能出出来了... 另外表示BZOJ Rank1 tql... 暴力的话,由每个\(A_i\)向它 ...

随机推荐

  1. ASP.NET Core 2.1 : 十四.静态文件与访问授权、防盗链

    我的网站的图片不想被公开浏览.下载.盗链怎么办?本文主要通过解读一下ASP.NET Core对于静态文件的处理方式的相关源码,来看一下为什么是wwwroot文件夹,如何修改或新增一个静态文件夹,为什么 ...

  2. Linux增加开放端口号

    Linux增加开放端口号 : 方法一:命令行方式 1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT   2.保存:/etc/ ...

  3. Friendly Date Ranges 让日期区间更友好

    把常见的日期格式如:YYYY-MM-DD 转换成一种更易读的格式. 易读格式应该是用月份名称代替月份数字,用序数词代替数字来表示天 (1st 代替 1). 记住不要显示那些可以被推测出来的信息: 如果 ...

  4. 【问题解决方案】AttributeError: module 'pygal' has no attribute 'Worldmap'

    <Python编程:从入门到实践>- 16章-16.2.5制作世界地图 import pygal 后报如标题的error 参考CSDN 解决:AttributeError: module ...

  5. 【zabbix教程系列】五、邮件报警设置(脚本方式)

    本方式是使用外部邮箱账号发送报警邮件到指定邮箱. 好处是:此邮箱账号既能发送邮件,也能接收邮件,而且避免被当做垃圾邮件. 一.zabbix-server端安装mailx服务 [root@ltt01 ~ ...

  6. centos7之zabbix服务器的常规优化

    一.硬件需求分析 1.首先我们来分析一个硬件需求,这里我以400个agent计算,CPU建议是4核,内存不要少于8GB,硬盘只要不是用了很久的主机就行,容量的话建议300GB基本就够使用好一段时间了, ...

  7. centos7之zabbix邮件报警(短信报警)

    前言 前面我们介绍了zabbix的基本linux和window及SNMP流量的简单监控,我们知道作为运维人员,需要7x24小时待命,但是我们不可能时时刻刻都坐在电脑旁边查看监控上的各个主机状态,所以我 ...

  8. pc端手機端自適應佈局方案

    https://blog.csdn.net/chose_DoIt/article/details/80424341 https://blog.csdn.net/cxz792116/article/de ...

  9. OpenJudge-bailian 3454 秦腾与教学评估

    http://bailian.openjudge.cn/practice/3454?lang=en_US 题目 在秦腾进入北京大学学习的第一个学期,就不幸遇到了前所未有的教学评估.在教学评估期间,同学 ...

  10. 基于Android P系统对selinux相关整理

    1.首先selinux是一种加强文件安全的一种策略.主要包含进程和文件对象. 在system\sepolicy\public\attributes文件中有: # All types used for ...