[Luogu P4600] [HEOI2012] [BZOJ 2746]旅行问题

14 篇文章 0 订阅
10 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

yz 是 Z 国的领导人,他规定每个地区的名字只能为 26 26 个小写拉丁字母的一个。由于地区数有可能超过 26 个,便产生了一个问题,如何辨别名字相同的地区?于是 yz 规定,一个地区的描述必须包含它的所有上级,且上级按次序排列。于是,一个地区的描述是一个字符串。比如说,一个地区的名字为 c c ,它的上级为 b b b 的上级为 a a a 没有上级,那么这个地区就描述为 abc 。显然,这个描述同时包含了 c c 的上级 b b b 的上级 a 的描述,分别为 ab a b a a

值得注意的是,每个地区最多有一个上级,同一上级的地区之间名字不同,没有上级的地区之间名字不同。

现在,yz 对外公布了 n 个地区的描述,这些描述中包含了 Z 国所有地区的描述,并让你处理来访者的旅行问题。

现有 m m 对人访问这个国家,对于每对人,第一个人喜欢第 i 个描述中的第 j j 个地区,设这个地区描述为 s1 ,第二个人喜欢第 k k 个描述中的第 l 个地区,设这个地区描述为 s2 s 2 。他们为了统一行程,决定访问描述为 s s 的地区(显然他们只关心地区的名字,并非是地区本身), 设 s 的长度为 t t s 需要满足以下条件:

1: tj t ≤ j ​ , tl t ≤ l ​ ;

1: s[1t]=s1[jt+1j] s [ 1 ⋯ t ] = s 1 [ j − t + 1 ⋯ j ] , s[1t]=s2[lt+1l] s [ 1 ⋯ t ] = s 2 [ l − t + 1 ⋯ l ] ;(即 s s s1 1 1 k 位与 s2 s 2 1 1 l位的公共后缀)

2: t t 最大化。 为了不使输出过大,你只需把这个字符串按照如下生成的 26 进制数转成 10 10 进制后 mod 1000000007 m o d   1000000007 后输出:

a->0

b->1

.

.

.

z->25

比如地区 cab 被编码成 2×262+0×261+1×260=1353 2 × 26 2 + 0 × 26 1 + 1 × 26 0 = 1353

输入输出格式

输入格式:

第一行给定一个整数 n n

2n+1 行:每 i+1 i + 1 行给定一个字符串 a[i] a [ i ] ,表示第 i i 个描述。

接下来一行一个整数 m

接下来 m m 行:每行给定四个整数 i,j,k,l ,字母含义与题目描述一致。

输出格式:

m m 行,每行一个整数,表示答案字符串的编码。

输入输出样例

输入样例#1:

2
aabb
babb
2
1 3 2 3
1 4 2 4 

输出样例#1:

1
1

说明

【样例说明】

询问 1 中的公共后有 ab 和 b,但是没有 ab 这个地区,只有 b 地区,所以只能选择 b 这个地区;

询问 2 中的公共后有 abb、bb 和 b,但是没有 abb 和 bb 这两个地区,只有 b 地区,所以只能选择 b 这个地区。

【数据范围】

设这个国家地区总数数为 tot(注意:输入的字符串总长度可能超过 tot!)

对于 30%的数据,满足 totmn100

对于 50%的数据,满足 totmn1000 t o t , m , n ≤ 1000

对于 80%的数据,满足 totmn100000 t o t , m , n ≤ 100000

对于 100%的数据,满足 totmn1000000 t o t , m , n ≤ 1000000

保证输入文件不超过 20MB。

解题分析

真是服了出题人, 这题目描述…

题意:给你 n n 个串, m次询问, 每次询问串 ai a i 的前 j j 位和串ak的前 l l 位的最长公共后缀, 并且其满足是某个串前缀的最长长度。

思路还是有点神的:某个串前缀一定是在我们构出的Trie树上, 而前缀的后缀可以通过跳 fail f a i l 链得到。 所以我们答案即为 ai[j] a i [ j ] ak[l] a k [ l ] Trie T r i e 树上的 LCA L C A 处的值。

那么建出 Trie T r i e 树跑一边即可。

PS:博主用的倍增 LCA L C A 会被卡空间,大家最好用树剖 LCA L C A …不过即使这样洛谷也可以过(数据太水)。

代码如下:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <queue>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define ll long long
#define MX 1000050
#define MOD 1000000007
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
ll hs[MX];
struct Node
{
    int to[26];
}t[MX];
int arr, num, q, cnt;
int lent[MX], pos[MX << 1], dep[MX << 1], up[MX << 1][22];
char buf[MX];
std::queue <int> que;
namespace AC
{
    void insert(const int &ord)
    {
        int l = std::strlen(buf + 1);
        R int now = 0, val;
        lent[ord] = l + lent[ord - 1];
        for (R int i = 1; i <= l; ++i)
        {
            val = buf[i] - 'a';
            if(!t[now].to[val])
            t[now].to[val] = ++arr,
            hs[t[now].to[val]] = (hs[now] * 26 + val) % MOD;
            pos[++cnt] = t[now].to[val];
            now = t[now].to[val];
        }
    }
    void getfail()
    {
        R int now, tar;
        for (R int i = 0; i < 26; ++i) if(t[0].to[i])
        {
            que.push(t[0].to[i]);
            up[t[0].to[i]][0] = 0;
            dep[t[0].to[i]] = 1;
        }
        W (!que.empty())
        {
            now = que.front(); que.pop();
            for (R int i = 0; i < 26; ++i)
            {
                if(!t[now].to[i])//在这里倍增的是fail链, 所以依然要一直保存最近的可转移的位置。
                {
                    t[now].to[i] = t[up[now][0]].to[i];
                    continue;
                }
                tar = t[now].to[i];
                up[tar][0] = t[up[now][0]].to[i];
                dep[tar] = dep[up[tar][0]] + 1;
                for (R int i = 1; i <= 21; ++i)
                {
                    if(up[tar][i - 1])
                    up[tar][i] = up[up[tar][i - 1]][i - 1];
                    else break;
                }
                que.push(tar);
            }
        }
    }
    IN int lca(R int x, R int y)
    {
        if(dep[x] < dep[y]) std::swap(x, y);
        int del = dep[x] - dep[y];
        for (R int i = 0; i <= 21; ++i)
        {
            if(del & (1 << i)) x = up[x][i], del -= (1 << i);
            if(!del) break;
        }
        if(x == y) return x;
        for (R int i = 20; ~i; --i)
        {
            if(up[x][i] != up[y][i])
            x = up[x][i], y = up[y][i];
        }
        return up[x][0];
    }
}
int main(void)
{
    in(num); R int x, xl, y, yl, p1, p2;
    for (R int i = 1; i <= num; ++i)
    {
        scanf("%s", buf + 1);
        AC::insert(i);
    } in(q);
    AC::getfail();
    W (q--)
    {
        in(x), in(xl), in(y), in(yl);
        p1 = pos[lent[x - 1] + xl], p2 = pos[lent[y - 1] + yl];
        printf("%lld\n", hs[AC::lca(p1, p2)]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值