• [TJOI2017] DNA


    乓乓球

    话说天津的玩梗真的是...

    正文

    做这个题,其实暴力一分也得不了....因为我的 (O(nlogn)) 不开 O2 最快的点也才跑了200+ms...然后大概就是这么想,我们在暴力的基础上优化一下。反正这样子应该可以简化反正就4个字符...

    就是说,我们预处理出每个(子串)东西的 LCP,然后搞一搞吧。但是我知道的求 LCP 的快速方法只有后缀数组的 height,然后自然的联想到了 RMQ, 也就是说我们查询 suf(x), suf(y) 的 LCP 可以通过找一个 (min_{x < i le y} (height[i])) 对吧,这是显然的。因为字典序的性质。

    算了,先说清楚步骤,再讲程序。

    1. 预处理出 height

    这个没什么好说的对吧

    2. 做出ST表

    这个也挺水的

    3. 暴力匹配

    这个注意如果不一样的超过三个就直接退出

    const int Maxn = 2e5 + 111;
    
    int T, len, lena, sa[Maxn], height[Maxn], rk[Maxn], x[Maxn], y[Maxn], se[Maxn], m = 127, ans, pos, f[Maxn][18], lenb;
    
    char c[Maxn];
    /*  后缀数组  */
    inline void get_sa()
    {
    	for(int i = 1; i <= len; ++i) ++se[x[i] = c[i]];
    	for(int i = 2; i <= m; ++i) se[i] += se[i - 1];
    	for(int i = len; i >= 1; --i) sa[se[x[i]]--] = i;
    	for(int k = 1; k <= len; k <<= 1)
    	{
    		int num = 0;
    		for(int i = len - k + 1; i <= len; ++i) y[++num] = i;
    		for(int i = 1; i <= len; ++i) if(sa[i] > k) y[++num] = sa[i] - k;
    		for(int i = 1; i <= m; ++i) se[i] = 0;
    		for(int i = 1; i <= len; ++i) ++se[x[i]];
    		for(int i = 2; i <= m; ++i) se[i] += se[i - 1];
    		for(int i = len; i >= 1; --i) sa[se[x[y[i]]]--] = y[i], y[i]= 0;
    		swap(x, y); x[sa[1]] = 1, num = 1;
    		for(int i = 2; i <= len; ++i)
    			x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i - 1] + k] == y[sa[i] + k])? num: ++num;
    		if(num == len) break;
    		m = num;
    	}
    	return ;
    }
    
    inline void get_height()
    {
    	for(int i = 1; i <= len; ++i) rk[sa[i]] = i;
    	int k = 0;
    	for(int i = 1; i <= len; ++i)
    	{
    		if(rk[i] == 1) continue;
    		if(k) --k;
    		int j = sa[rk[i] - 1];
    		while(j + k <= len && i + k <= len && c[j + k] == c[i + k]) ++k;
    		height[rk[i]] = k;
    	}
    }
    
    inline int query(int a, int b)
    {
    	int k = log2(b - a + 1);
    	return min(f[a][k], f[b - (1 << k) + 1][k]);
    }
    
    inline int LCP(int a, int b)
    {
    	a = rk[a];
    	b = rk[b];
    	if(a > b) swap(a, b);
    	return query(++a, b);
    }
    
    int main()
    {
    #ifdef _DEBUG
    	freopen("in.txt", "r", stdin);
    #endif
    	T = rd();
    	while(T--)
    	{
    		scanf("%s", c + 1);
    		lena = strlen(c + 1);
    		c[lena + 1] = '#';
    		scanf("%s", c + lena + 2);
    		len = strlen(c + 1);
    		lenb = len - lena -1;
    		m = 127;
    		for(int i = 1; i <= 127; ++i) se[i] = 0;
    		get_sa();
    		get_height();
            /*  RMQ  */
    		for(int i = 1; i <= len; ++i) f[i][0] = height[i];
    		int tmp = log2(len);
    		for(int i = 1; i <= tmp; ++i)
    		{
    			int k = 1 << i;
    			for(int j = 1; j <= len - k + 1; ++j) f[j][i] = min(f[j][i - 1], f[j + k / 2][i - 1]);
    		}
            /*  暴力匹配  */
    		ans = 0;
    		for(int i = 0; i <= lena - lenb; ++i)
    		{
    			int tot = 0;
    			for(int j = 1; j <= lenb && tot <= 3;)
    			{
    				if(c[i + j] != c[lena + 1 + j])
    				{
    					
    					tot++, ++j;
    					if(tot > 3) break;
    				}
    				else j += LCP(i + j, lena + j + 1);
    			}
    			if(tot <= 3) ans++;
    		}
    		printf("%d
    ", ans);
    	}
    	return 0;
    }
    

    关于这么做的正确性

    我们证明一个简单的结论,也许就是你所疑惑的

    (LCP(i, j)) 即是 (suf(i), suf(j)) 的最长公共前缀应该是等于 (min (height[k])) 其中 (rk[i] < k le rk[j]),不妨设 (rk_i < rk_j),我们首先 (height[i]) 是啥,是 (LCP(sa[i - 1], sa[i])) ,是排名第 (i) 的子串和排名第 (i-1) 的子串的最长公共前缀,意会一下,根据字典序的排序的性质,排名为 (rk[i]) 的子串和排名为 (rk[j]) 的子串,我们只要把排名之间的最小公共前缀求出即可。就是做一个 RMQ
    有意向的还可以看一下 ural 1297 ,也是这样的。

    正在想有没有不开O2就能过的

  • 相关阅读:
    课下作业--微软拼音输入法评价
    课堂练习--找水王
    第十四周总结
    第一阶段意见评论
    第十三周总结
    梦断代码阅读笔记03
    第十二周总结
    用户模板与用户场景
    2020年寒假假期总结0205
    2020年寒假假期总结0204
  • 原文地址:https://www.cnblogs.com/zhltao/p/12623838.html
Copyright © 2020-2023  润新知