zoukankan      html  css  js  c++  java
  • HDU7084/2021“MINIEYE杯”中国大学生算法设计超级联赛(10)1008. Pty loves string(Border/KMP Fail树/DFS序/主席树)

    Problem Description

    Pty has a string S of length n consisting of lowercase English letters. He denotes the value of string T as the number of occurrences of T in string S.

    Now he has Q queries, for each query he gives you x,y. Let the string T be the concatenation of the prefix of length x in the string S and its suffix of length y. He wants you to tell him the value of T.

    Input

    An integer T in the first line indicates the number of tests.

    For each test,first line contains two integer n,Q - the length of S and the number of queries.

    The second line contains string S of length n consisting of lowercase English letters.

    For the next Q lines, each line contains two integers x,y.

    1≤T≤5,1≤n,Q≤2×105,1≤x,y≤n

    Output

    For each test,output Q lines in total.

    For each query, print the answer in one line.

    Sample Input

    1
    6 3
    ababaa
    1 1
    2 1
    3 1
    

    Sample Output

    1
    2
    1
    

    Debug一年终于把这个题过了,原因在于询问次数q打成了n。。。

    首先一看问的是串的出现次数,可能会想到kmp,但暴力kmp显然不可做。又注意到每次的询问给出的x和y和原串前后缀有关,就考虑从前缀和后缀这里入手。对于这个题,设给定的字符串为(s),每个询问给出一对前后缀(s[1,x])(s[n - y + 1, n]),不妨设其拼起来后在(s)([p, q])这个位置出现了,那么(s[1,x])(s[p, p + x - 1])相等,(s[n - y + 1, n])(s[q - y + 1,q])相等,同时有(q - y + 1 = (p + x - 1) + 1)。直接的思想就是找所有和(s[1, x])相等的子串以及所有和(s[n - y + 1, n])相等的子串检查他们能否拼接在一起。那么怎么找呢?这时就需要用到(Border)这个知识点了,相关知识可以参考洛谷P5829这道题。我们正着和反着分别建出(next)树以后,会发现(p + x - 1)一定在(x)的子树,(q - y + 1)一定在(n - y + 1)的子树(因为y表示的是后缀长度)。举个例子,设(s=‘ababab’)(x = 2),那么(next[4])(next[6])都为2,都在2的子树里,观察也可以发现后两个ab都和第一个ab(前缀)一样。也可以自己构造一下加深理解(关键是要知道(Border)(Border)还是(Border)以及理解(next)树的具体含义)。

    所以现在问题转化为给出两棵树,树上每个点都有一个值,然后每次询问给出x和y两个子树,问两棵子树中有多少个点对((a, b))满足(a + 1 = b)(因为是正着和倒着建的next树,所以有这个关系)。因为树上问题可以通过(dfs)序转化为序列问题,这就类似给出两个序列,询问子列的交集。此时就需要用到主席树了。建出两棵(next)树以后分别进行(dfs)求出来(dfs)序,然后我们沿着第一棵树的(dfs)序建主席树。这里有一个很关键的问题就是两棵树的形状可能不一样,怎么处理节点相等这个对应关系。这里有一个很巧妙的方式就是在处理(dfs)序的时候同时获得第一棵树上某个dfs序所对应的真实的值(即下标)存到id数组里,然后对于1~n + 1遍历的时候(为什么是n + 1?因为next树的根是0号节点,所以dfs序是1~n + 1),增加的位置是(dfn2[id1[i] + 1])(id1[i] + 1)就是对应的(a + 1 = b)这个关系,然后(dfn2[id1[i] + 1])就把对应的b转到第二棵树的相应的dfs序位置了。建好树以后不断读入查询即可,查询的就是在x这个子树对应的区间里,有多少y这个子树对应区间内的(dfn)值落进去了(因为已经把下标的对应关系转为了(dfn)值的对应关系),常规的主席树套路。

    #include <bits/stdc++.h>
    #define N 1000005
    using namespace std;
    int n, q;
    char s[N];
    int nxt1[N], nxt2[N];
    int head1[N], ver1[2 * N], Next1[2 * N], tot1 = 0;
    int head2[N], ver2[2 * N], Next2[2 * N], tot2 = 0;
    void add1(int x, int y) {
    	ver1[++tot1] = y, Next1[tot1] = head1[x], head1[x] = tot1;
    }
    void add2(int x, int y) {
    	ver2[++tot2] = y, Next2[tot2] = head2[x], head2[x] = tot2;
    }
    vector<int> node1[N], node2[N];
    void getnxt1() {
    	nxt1[1] = 0;
    	int n = strlen(s + 1);
    	for(int i = 2, j = 0; i <= n; i++) {
    		while(j != 0 && s[i] != s[j + 1]) j = nxt1[j];
    		if(s[i] == s[j + 1]) j++;
    		nxt1[i] = j;
    	}
    	for(int i = 1; i <= n; i++) {
    		add1(nxt1[i], i);
    	}
    }
    void getnxt2() {
    	int n = strlen(s + 1);
    	nxt2[n] = n + 1;
    	for(int i = n - 1, j = n + 1; i >= 1; i--) {
    		while(j != n + 1 && s[i] != s[j - 1]) j = nxt2[j];
    		if(s[i] == s[j - 1]) j--;
    		nxt2[i] = j;
    	}
    	for(int i = 1; i <= n; i++) {
    		add2(nxt2[i], i);
    	}
    }
    int dfn1[N], dfn2[N], id1[N], id2[N], sz1[N], sz2[N], cnt1 = 0, cnt2 = 0;
    int ed1[N], ed2[N];
    void dfs1(int x, int pre) {
    	sz1[x] = 1;
    	dfn1[x] = ++cnt1;
    	id1[cnt1] = x;
    	for(int i = head1[x]; i; i = Next1[i]) {
    		int y = ver1[i];
    		if(y == pre) continue;
    		dfs1(y, x);
    		sz1[x] += sz1[y];
    	}
    	ed1[x] = cnt1;
    }
    void dfs2(int x, int pre) {
    	sz2[x] = 1;
    	dfn2[x] = ++cnt2;
    	id2[cnt2] = x;
    	for(int i = head2[x]; i; i = Next2[i]) {
    		int y = ver2[i];
    		if(y == pre) continue;
    		dfs2(y, x);
    		sz2[x] += sz2[y];
    	}
    	ed2[x] = cnt2;
    }
    int cnt = 0;//总的节点数
    int sum[N * 20], L[N * 20], R[N * 20];//主席树一定不能吝啬空间
    int T[N];
    int build(int l, int r) {
    	int rt = ++cnt;
    	sum[rt] = 0;
    	int mid = (l + r) >> 1;
    	if(l < r) {
    		L[rt] = build(l, mid);
    		R[rt] = build(mid + 1, r);
    	}
    	return rt;
    }
    int modify(int pre, int l, int r, int x) {
    	int rt = ++cnt;
    	L[rt] = L[pre]; R[rt] = R[pre];
    	sum[rt] = sum[pre] + 1;
    	if(l < r) {
    		int mid = (l + r) >> 1;
    		if(x <= mid) L[rt] = modify(L[pre], l, mid, x);
    		//注意这里是x <= mid, 而不是线段树常见写法
    		//因为这是对权值建立的线段树!
    		else R[rt] = modify(R[pre], mid + 1, r, x);
    	}
    	return rt;
    }
    int query(int u, int v, int LFT, int RT, int l, int r) {
    	if(LFT > r || RT < l) return 0;
        int x = sum[v] - sum[u];
        if(l >= LFT && r <= RT) return x;
        int mid = (l + r) >> 1;
        return query(L[u], L[v], LFT, RT, l, mid) + query(R[u], R[v], LFT, RT, mid + 1, r);
    }
    int main() {
    	int t;
    	cin >> t;
    	while(t--) {
    		tot1 = tot2 = 0;
    		cnt1 = cnt2 = 0;
    		cnt = 0;
    		memset(s, 0, sizeof(s));
    		memset(head1, 0, sizeof(head1));
    		memset(head2, 0, sizeof(head2));
    		scanf("%d%d", &n, &q);
    		scanf("%s", s + 1);
    		getnxt1();
    		getnxt2();
    		dfs1(0, -1);
    		dfs2(n + 1, -1);
    		T[0] = build(1, n + 1);
    		for(int i = 1; i <= n + 1; i++) {
    			T[i] = modify(T[i - 1], 1, n + 1, dfn2[id1[i] + 1]);
    		}
    		for(int i = 1; i <= q; i++) {
    			int x, y;
    			cin >> x >> y;
    			y = n - y + 1;
    		 	cout << query(T[dfn1[x] - 1], T[ed1[x]], dfn2[y], ed2[y], 1, n + 1) << endl;
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    十六.jQuery源码解析之Sizzle设计思路.htm
    关于微信浏览不能URL传参,URL中的问号被删除
    websocket 通信协议
    java_httpservice
    Socket.Io 做个标记 下来了解下
    通过netty实现服务端与客户端的长连接通讯,及心跳检测。
    NETTY 编码器介绍
    Netty4.0学习教程
    FORM表单不刷新提交POST数据
    Linux0.11学习
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/15175330.html
Copyright © 2011-2022 走看看