zoukankan      html  css  js  c++  java
  • [NOI2018] 你的名字(SAM+主席树)

    Description

    实力强大的小 A 被选为了 ION2018 的出题人,现在他需要解决题目的命名问题。

    小 A 被选为了 ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

    由于 ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同

    由于一些特殊的原因,小 A 不知道 ION2017 每道题的名字,但是他通过一些特殊手段得到了 ION2017 的命名串,现在小 A 有 \(Q\) 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。

    由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

    【输入格式】
    第二行一个正整数 \(Q\),表示询问次数。

    接下来 \(Q\) 行,每行有一个字符串 \(T\) 和两个正整数 \(l, r\),表示询问如果 ION2017 的命名串是 \(S[l..r]\),ION2018 的命名串是 \(T\) 的话,有几种命名方式一定满足规定。

    保证输入中给出的字符串都是由小写字母构成的。

    【输出格式】
    输出 \(Q\) 行,第 \(i\) 行一个非负整数表示第 \(i\) 个询问的答案。

    【输入样例】

    scbamgepe
    3
    smape 2 7
    sbape 3 8
    sgepe 1 9
    

    【输出样例】

    12
    10
    4
    

    【数据范围与约定】

    测试点 \(\lvert S \rvert \leq\) \(Q \leq\) \(\sum \lvert T \rvert \leq\) 询问限制 其他限制
    1 \(200\) \(200\) \(40000\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) \(\lvert T \rvert \leq 200\)
    2 \(1000\) \(200\) \(40000\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) \(\lvert T \rvert \leq 200\)
    3 \(1000\) \(200\) \(40000\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) \(\lvert T \rvert \leq 200\)
    4 \(1000\) \(200\) \(5 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    5 \(1000\) \(200\) \(5 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    6 \(5 \times 10^5\) \(1\) \(5 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    7 \(5 \times 10^5\) \(1\) \(5 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    8 \(10^5\) \(10^5\) \(2 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    9 \(10^5\) \(10^5\) \(2 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) 字符串随机
    10 \(2 \times 10^5\) \(10^5\) \(4 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    11 \(2 \times 10^5\) \(10^5\) \(4 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) 字符串随机
    12 \(3 \times 10^5\) \(10^5\) \(6 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    13 \(3 \times 10^5\) \(10^5\) \(6 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) 字符串随机
    14 \(4 \times 10^5\) \(10^5\) \(8 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    15 \(4 \times 10^5\) \(10^5\) \(8 \times 10^5\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) 字符串随机
    16 \(5 \times 10^5\) \(10^5\) \(10^6\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\)
    17 \(5 \times 10^5\) \(10^5\) \(10^6\) 对于所有询问有 \(l = 1, r = \lvert S \rvert\) 字符串随机
    18 \(2 \times 10^5\) \(10^5\) \(10^6\)
    19 \(3 \times 10^5\) \(10^5\) \(10^6\)
    20 \(4 \times 10^5\) \(10^5\) \(10^6\)
    21 \(5 \times 10^5\) \(10^5\) \(10^6\)
    22 \(5 \times 10^5\) \(10^5\) \(10^6\)
    23 \(5 \times 10^5\) \(10^5\) \(10^6\)
    24 \(5 \times 10^5\) \(10^5\) \(10^6\)
    25 \(5 \times 10^5\) \(10^5\) \(10^6\)

    对于所有数据,保证 \(1 \leq l \leq r \leq \lvert S \rvert\)\(1 \leq \lvert T \rvert \leq 5 \times 10^5\)

    Solution

    先简化题意:求 \(T\) 有多少个本质不同的子串,不是 \(S[l,r]\) 的子串。

    考虑对于每个 \(i\) 求出一个最小的 \(j\),满足 \(T[j,i]\)\(S[l,r]\) 的子串,那么对于任意 \(1\leq k<j\) 都满足 \(T[k,i]\) 不是 \(S[l,r]\) 的子串。显然随着 \(i\) 的增大,\(j\) 不会减小。那么考虑双指针做法:一开始令 \(j=1\)。对于每个 \(i\),让 \(j\) 的值不断增加,直到 \(T[j,i]\)\(S[l,r]\) 的子串。

    考虑如何快速模拟这个过程:先建出 \(S\) 串的后缀自动机。接着对于每次询问,维护一个指针 \(p\),一开始 \(p\) 指向后缀自动机的根节点。对于每个 \(i\),设 \(p\) 走字符 \(T[i]\) 的边到达的点为 \(u\)。接下来要判断节点 \(u\) 对应的长度为 \(i-j+1\) 字符串是否为 \(S[l,r]\) 的子串。

    考虑如何快速判断:一开始建后缀自动机的时候,对每个节点 \(x\) 维护两个标记 \(pos_x,yes_x\)。若 \(x\) 表示的其中一个字符串是 \(S\) 的前缀,前缀长度为 \(len\),则 \(pos_x=len,yes_x=1\),否则 \(pos_x\) 等于 \(parent\) 树上 \(x\) 子树中的任意一个 \(pos_x\)\(yes_x=0\)。显然 \(\lceil\) 节点 \(u\) 对应字符串的 \(right\) 集合\(\rfloor\) 就是 \(\lceil\) \(parent\) 树上 \(u\) 子树中 \(pos_x\) 的并集 \(\rfloor\)。而判断 \(u\) 对应的长度为 \(y\) 的字符串是否是 \(S[l,r]\) 的子串,也就是判断 \(u\) 子树中是否存在一个 \(pos_x∈[l+len-1,r]\)。这个可以用线段树合并或者主席树快速求出。

    回到原问题,若节点 \(u\) 对应的长度为 \(i-j+1\) 字符串是 \(S[l,r]\) 的子串,则令 \(p\) 指向节点 \(u\),令 \(lim_i=i-j+1\) 并结束循环。否则令 \(j\) 的值 \(+1\),为了保证 \(p\) 所在的节点能代表字符串 \(T[j,i-1]\),如果 \(i-j=maxl_{fa_p}\),令 \(p=fa_p\)

    因为求的是 \(T\) 中本质不同的合法子串有几个,所以把 \(T\) 串的后缀自动机也建出来。然后枚举这个后缀自动机上的每个节点 \(x\),令答案加上 \(max(0,maxl_x-max(lim_{pos_x},maxl_{fa_x}))\),含义是减去重复出现的子串以及非法的子串。

    时间复杂度 \(O(\sum|T|\log|S|)\)

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    const int e = 1e6 + 5;
    struct point
    {
    	int go[26];
    	inline void init()
    	{
    		memset(go, 0, sizeof(go));
    	}
    }t1[e], t2[e];
    struct node
    {
    	int l, r, cnt;
    }c[e * 20];
    char a[e];
    int nxt[e], go[e], adj[e], fa1[e], fa2[e], tot, n, m, maxl1[e], pos[e], id[e];
    int lim[e], lst, maxl2[e], num, rt[e], st[e], ed[e], pool, tim;
    bool yes[e];
    long long ans;
    
    inline void insert1(char ch)
    {
    	int c = ch - 'a', i = lst;
    	t1[lst = ++tot].init();
    	maxl1[lst] = maxl1[i] + 1;
    	yes[lst] = 1;
    	for (; i && !t1[i].go[c]; i = fa1[i]) t1[i].go[c] = lst;
    	if (!i) fa1[lst] = 1;
    	else
    	{
    		int j = t1[i].go[c];
    		if (maxl1[j] == maxl1[i] + 1) fa1[lst] = j;
    		else
    		{
    			int p;
    			t1[p = ++tot] = t1[j];
    			fa1[p] = fa1[j];
    			fa1[j] = fa1[lst] = p;
    			maxl1[p] = maxl1[i] + 1;
    			for (; i && t1[i].go[c] == j; i = fa1[i]) t1[i].go[c] = p; 
    		}
    	}
    }
    
    inline void insert2(char ch, int x)
    {
    	int c = ch - 'a', i = lst;
    	t2[lst = ++tot].init();
    	maxl2[lst] = maxl2[i] + 1;
    	pos[lst] = x;
    	for (; i && !t2[i].go[c]; i = fa2[i]) t2[i].go[c] = lst;
    	if (!i) fa2[lst] = 1;
    	else
    	{
    		int j = t2[i].go[c];
    		if (maxl2[j] == maxl2[i] + 1) fa2[lst] = j;
    		else
    		{
    			int p;
    			t2[p = ++tot] = t2[j];
    			fa2[p] = fa2[j];
    			pos[p] = x;
    			fa2[j] = fa2[lst] = p;
    			maxl2[p] = maxl2[i] + 1;
    			for (; i && t2[i].go[c] == j; i = fa2[i]) t2[i].go[c] = p; 
    		}
    	}
    }
    
    inline void add(int x, int y)
    {
    	nxt[++num] = adj[x];
    	adj[x] = num;
    	go[num] = y;
    }
    
    inline void insert(int y, int &x, int l, int r, int v)
    {
    	c[x = ++pool] = c[y];
    	c[x].cnt++;
    	if (l == r) return;
    	int mid = l + r >> 1;
    	if (v <= mid) insert(c[y].l, c[x].l, l, mid, v);
    	else insert(c[y].r, c[x].r, mid + 1, r, v);
    }
    
    inline int query(int x, int l, int r, int s, int t)
    {
    	if (!x) return 0;
    	if (l == s && r == t) return c[x].cnt;
    	int mid = l + r >> 1;
    	if (t <= mid) return query(c[x].l, l, mid, s, t);
    	else if (s > mid) return query(c[x].r, mid + 1, r, s, t);
    	else
    	return query(c[x].l, l, mid, s, mid) + query(c[x].r, mid + 1, r, mid + 1, t);
    }
    
    inline bool check(int x, int l, int r)
    {
    	return x && query(rt[ed[x]], 1, n, l, r) > query(rt[st[x] - 1], 1, n, l, r);
    }
    
    inline void dfs(int u)
    {
    	st[u] = ++tim;
    	id[tim] = u;
    	for (int i = adj[u]; i; i = nxt[i]) dfs(go[i]);
    	ed[u] = tim;
    }
    
    inline void build()
    {
    	int i;
    	for (i = 1; i <= n; i++) insert1(a[i]);
    	for (i = 2; i <= tot; i++) add(fa1[i], i);
    	dfs(1);
    	for (i = 1; i <= tot; i++)
    	if (yes[id[i]]) insert(rt[i - 1], rt[i], 1, n, maxl1[id[i]]);
    	else rt[i] = rt[i - 1];
    }
    
    int main()
    {
    	int i, l, r;
    	tot = lst = 1;
    	scanf("%s", a + 1);
    	n = strlen(a + 1);
    	build();
    	scanf("%d", &m);
    	while (m--)
    	{
    		tot = lst = 1;
    		t2[1].init();
    		int p = 1, nowl = 0;
    		scanf("%s%d%d", a + 1, &l, &r);
    		int len = strlen(a + 1);
    		for (i = 1; i <= len; i++)
    		{
    			int c = a[i] - 'a';
    			insert2(a[i], i);
    			for (;;)
    			{
    				if (check(t1[p].go[c], l + nowl, r))
    				{
    					nowl++;
    					p = t1[p].go[c];
    					break;
    				}
    				if (nowl == 0) break;
    				nowl--;
    				if (nowl == maxl1[fa1[p]]) p = fa1[p];
    			}
    			lim[i] = nowl;
    		}
    		ans = 0;
    		for (i = 2; i <= tot; i++)
    		ans += max(0, maxl2[i] - max(maxl2[fa2[i]], lim[pos[i]]));
    		printf("%lld\n", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    第几天?
    农历02__资料
    农历01
    VC6_预编译头
    QWebEngine_C++_交互
    Qt570_CentOS64x64_02
    Qt570_CentOS64x64_01
    QWebEngineView_CssVariables
    Windows__书
    Win7SDK
  • 原文地址:https://www.cnblogs.com/cyf32768/p/12196028.html
Copyright © 2011-2022 走看看