P4770 [NOI2018] 你的名字

传送门

题目大意

一个长为 \(n(1\le n\le5\cdot10^5)\) 的串 \(s\)\(q(1\le q\le10^5)\) 次询问,每次询问 \(t\space l\space r(1\le|t|\le5\cdot10^5,\sum|t|\le10^6,1\le l\le r\le n)\) ,求有多少 \(t\) 的本质不同子串,其不是 \(s[l:r]\) 的子串。

思路

考虑对于每个询问,求出 \(t\) 的本质不同子串数,然后再减去 \(t\)\(s[l:r]\) 的本质不同非空公共子串数量即为答案。我们对 \(s\)\(t\) 分别建立 \(sam\)\(t\) 的本质不同子串个数很容易求出,接下来我们考虑 \(t\) 的每一个前缀,需要找到它的一个最长的后缀,这个后缀为 \(s[l:r]\) 的子串,记这个后缀的长度为 \(len\) ,那么 \(t\) 的这一个前缀中所有长度 \(\le len\) 的后缀就都是 \(s[l:r]\) 的子串,反之则不是。对于 \(t\)\(sam\) 上的每个节点,其对应的 \(len\) 就为其后缀树子树内所有节点 \(len\) 的最大值。对于 \(len\) ,我们可以用 \(t\)\(s\)\(sam\) 上跑一遍匹配来求得,我们用双指针 \(l,r\) 来表示当前待匹配的 \(t\) 子串, \(now\)\(t[l:r-1]\)\(s\)\(sam\) 上匹配的状态,考虑 \(t_r\) 为新加入的字符,如果当前节点有这条边,并且转移过去的状态出现在 \(s[l:r]\) 中,即可直接匹配,对于判断是否出现在 \(s[l:r]\) 中,我们用线段树合并来维护 \(s\)\(sam\) 上每个节点的 \(endpos\) 集合,这样转移时直接进行查询即可判断。如果不能转移,则不断将 \(l+1\) 。过程中要注意检查当前字符串 \(t[l:r-1]\) 的长度,如果小于 \(now\) 的长度,则跳到 \(now\) 在后缀树上的父亲即可。于是我们求出了所有我们需要的信息,最后求解答案即可,复杂度 \(O(|s|+\sum|t|log|s|)\)

代码

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-10;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;

LL ans = 0;
int cnt = 0, root[maxn * 30], N, ed;
struct sgt {
    int lc, rc;
}tr[maxn * 30];

int build()
{
    ++cnt, tr[cnt].lc = tr[cnt].rc = 0;
    return cnt;
}

void modify(int p, int l, int r, int val)
{
    if (l + 1 == r)
        return;
    int mid = (l + r) / 2;
    if (val < mid)
    {
        if (!tr[p].lc)
            tr[p].lc = build();
        modify(tr[p].lc, l, mid, val);
    }
    else
    {
        if (!tr[p].rc)
            tr[p].rc = build();
        modify(tr[p].rc, mid, r, val);
    }
}

bool query(int p, int l, int r, int a, int b)
{
    if (!p)
        return false;
    if (l >= a && r <= b)
        return true;
    int mid = (l + r) / 2;
    if (b <= mid)
        return query(tr[p].lc, l, mid, a, b);
    else if (a >= mid)
        return query(tr[p].rc, mid, r, a, b);
    else
        return query(tr[p].lc, l, mid, a, mid) | query(tr[p].rc, mid, r, mid, b);
}

int merge(int p, int q, int l, int r)
{
    if (!p)
        return q;
    if (!q)
        return p;
    int ne = build();
    if (l + 1 == r)
        return ne;
    int mid = (l + r) / 2;
    tr[ne].lc = merge(tr[p].lc, tr[q].lc, l, mid);
    tr[ne].rc = merge(tr[p].rc, tr[q].rc, mid, r);
    return ne;
}

int tot = 1, last = 1, leng[maxn * 3];

struct Node {
    int len, fa;
    int ch[26];
    bool isnp;
}sam[maxn * 6];

void extend(int c, int t)
{
    int p = last, np = last = ++tot;
    sam[np].len = sam[p].len + 1;
    sam[np].isnp = true;
    if (!t)
        root[np] = build(), modify(root[np], 1, N + 1, sam[np].len);
    for (; p && !sam[p].ch[c]; p = sam[p].fa)
        sam[p].ch[c] = np;
    if (!p)
        sam[np].fa = (t ? ed + 1 : 1);
    else
    {
        int q = sam[p].ch[c];
        if (sam[q].len == sam[p].len + 1)
            sam[np].fa = q;
        else
        {
            int nq = ++tot;
            sam[nq] = sam[q], sam[nq].len = sam[p].len + 1;
            sam[nq].isnp = false;
            if (!t)
                root[nq] = build();
            sam[q].fa = sam[np].fa = nq;
            for (; p && sam[p].ch[c] == q; p = sam[p].fa)
                sam[p].ch[c] = nq;
        }
    }
}

vector<int>G[maxn * 3], H[maxn * 3];

void add_edge1(int from, int to)
{
    G[from].push_back(to);
}

void add_edge2(int from, int to)
{
    H[from].push_back(to);
}

void dfs1(int v)
{
    for (auto& to : G[v])
        dfs1(to), root[v] = merge(root[v], root[to], 1, N + 1);
}

int dfs2(int v)
{
    int num = 0;
    if (sam[v].isnp)
        num = leng[sam[v].len - 1];
    for (auto& to : H[v])
        num = max(num, dfs2(to));
    if (v != ed + 1)
        ans -= max(0, min(num, sam[v].len) - sam[sam[v].fa].len);
    return num;
}

string S, T;
int Q;

void match(int a, int b)//[a,b]
{
    int l = 0, now = 1;
    for (int r = 0; r < T.length(); r++)
    {
        while (!sam[now].ch[T[r] - 'a'] || !query(root[sam[now].ch[T[r] - 'a']], 1, N + 1, a + r - l, b + 1))
        {
            l++;
            if (l > r)
            {
                now = 1;
                break;
            }
            if (r - l <= sam[sam[now].fa].len)
                now = sam[now].fa;
        }
        leng[r] = r - l + 1;
        if (leng[r])
            now = sam[now].ch[T[r] - 'a'];
    }
}

void solve()
{
    N = S.length(), root[1] = build();
    for (int i = 0; i < N; i++)
        extend(S[i] - 'a', 0);
    ed = tot; 
    ++tot, last = tot;
    for (int i = 2; i <= tot; i++)
        add_edge1(sam[i].fa, i);
    dfs1(1);
    int l, r;
    for (int i = 1; i <= Q; i++)
    {
        ans = 0;
        cin >> T >> l >> r;
        for (int j = 0; j < T.length(); j++)
            extend(T[j] - 'a', 1);
        for (int j = ed + 2; j <= tot; j++)
            ans += (LL)sam[j].len - (LL)sam[sam[j].fa].len;
        match(l, r);
        for (int j = ed + 1; j <= tot; j++)
            H[j].clear();
        for (int j = ed + 2; j <= tot; j++)
            add_edge2(sam[j].fa, j);
        dfs2(ed + 1);
        cout << ans << endl;
        for (int j = ed + 1; j <= tot; j++)
        {
            sam[j].fa = sam[j].isnp = sam[j].len = 0;
            for (int k = 0; k < 26; k++)
                sam[j].ch[k] = 0;
        }
        tot = ed + 1, last = tot;
    }
}

int main()
{
	IOS;
    cin >> S >> Q;
    solve();

	return 0;
}
posted @ 2022-07-15 11:32  Prgl  阅读(29)  评论(0)    收藏  举报