zoukankan      html  css  js  c++  java
  • 洛谷P3435 [POI2006]OKR-Periods of Words

    题目

    传送门

    这题意不是一般人能读懂的,为了读懂题目,我还特意去翻了题解[手动笑哭]

    题目大意:

    给定一个字符串s

    对于(s)的每一个前缀子串(s1),规定一个字符串(Q),(Q)满足:(Q)(s1)的前缀子串且Q不等于(s1)(s1)是字符串(Q+Q)的前缀.设(siz)为所有满足条件的(Q)(Q)的最大长度(注意这里仅仅针对(s1)而不是(s),即一个(siz)的值对应一个(s1))

    求出所有(siz)的和

    不要被这句话误导了:

    求给定字符串所有前缀的最大周期长度之和

    正确断句:求给定字符串 所有/前缀的最大周期长度/之和

    我就想了半天:既然是"最大周期长度",那不是唯一的吗?为什么还要求和呢?

    思路

    其实这题要AC并不难(看通过率就知道)

    看图

    要满足(Q)(s1)的前缀,则(Q)(1)~(5)位和(s1)的1~5位是一样的,又因为(s1)(Q+Q)的前缀,所以又要满足(s1)的6~8位和(Q+Q)的6~8位一样,即(s1)的6~8位和Q的1~3位相等,回到(s1),标蓝色的两个位置相等.

    回顾下KMP中(next)数组的定义:next[i]表示对于某个字符串a,"a中长度为next[i]的前缀子串"与"a中以第i为结尾,长度为next[i]的非前缀子串"相等,且next[i]取最大值

    是不是悟到了什么,是不是感觉这题和(next)数组冥冥之中有某种相似之处?

    但是,这仅仅只是开始

    按照题目的意思,我们要让(Q)的长度最大,也就是图中蓝色部分长度最小,但是(next)中存的是蓝色部分的最大值,显然,两者相违背,难道我们要改造(next)数组吗?明显不行,若(next)存储的改为最小值,则原来求(next)的方法行不通.考虑换一种思路(一定要对KMP中(next)的求法理解透彻,不然下面看不懂,不行的复习一下),我们知道对于next[i],next[next[i-1]],next[next[next[i]]]...都能满足"前缀等于以(i)结尾的子串"这个条件,且越往后,值越小,所以,我们的目标就定在上面序列中从后往前第一个不为0的(next)

    极端条件下,暴力跑可以去到(O(n^2)),理论上会超时(我没试过)

    两种优化:

    1. 记忆化,时间效率应该是O(n)这里不详细讲,可以去到洛谷题解查看
    2. 倍增(我第一时间想到并AC的做法):
      我们将j=next[j]这一语句称作"j跳了一次"(感觉怪怪的),将next拓展为2维,next[i][k]表示结尾为i,j跳了2^k的前缀字符长度(也就是next[i][0]等价于原来的next[i])
      借助倍增LCA的思想(没学没关系,现学现用),这里不做赘述,上代码
    		int tmp = i;
    		for(rr int j = siz[i] ; j >= 0 ; --j)//siz[i]是next[i][j]中第一个为0的小标j,注意倒序枚举
    			if(next[tmp][j] != 0)//如果不为0则跳
    				tmp = next[tmp][j];
    

    倍增方法在字符串长度去到(10^6)时是非常危险的,带个(log)理论是(2cdot 10^7)左右,常数再大那么一丢丢就TLE了,还好数据比较水,但是作为倍增和KMP的练习做一下也是不错的

    最后,记得开longlong(不然我就一次AC了)

    完整代码

    #include <iostream>
    #include <cmath>
    #include <cstdio>
    #define nn 1000010
    #define rr register
    #define ll long long
    using namespace std;
    int next[nn][30] ;
    int siz[nn];
    char s[nn];
    int n;
    int main() {
    //	freopen("P3435_3.in" , "r" , stdin);
    	cin >> n;
    	do
    		s[1] = getchar();
    	while(s[1] < 'a' || s[1] > 'z');
    	for(rr int i = 2 ; i <= n ; i++)
    		s[i] = getchar();
    	
    	next[1][0] = 0;
    	for(rr int i = 2 , j = 0 ; i <= n ; i++) {
    		while(j != 0 && s[i] != s[j + 1])
    			j = next[j][0];
    		if(s[j + 1] == s[i])
    			++j;
    		next[i][0] = j;
    	}
    	
    	rr int k = log(n) / log(2) + 1;
    	for(rr int j = 1 ; j <= k ; j++)
    		for(rr int i = 1 ; i <= n ; i++) {
    			next[i][j] = next[next[i][j - 1]][j - 1];
    			if(next[i][j] == 0)
    				siz[i] = j;
    		}
    	ll ans = 0;
    	for(rr int i = 1 ; i <= n ; ++i) {
    		int tmp = i;
    		for(rr int j = siz[i] ; j >= 0 ; --j)
    			if(next[tmp][j] != 0)
    				tmp = next[tmp][j];
    		if(2 * (i - tmp) >= i && tmp != i)
    			ans += (ll)i - tmp;
    	}
    	cout << ans;
    	return 0;
    } 
    
  • 相关阅读:
    Ubuntu12.04 安装网卡驱动
    C++类内存分布
    C++多态的实现及原理详细解析
    QT4.7.4在ubuntu10.10下的编译(转)
    FFMpeg处理RTMP流有两种方式
    C++中的单例模式
    C++ STL--stack/queue 的使用方法
    qt编译出现 /usr/bin/ld: cannot find -lQtCore的解决方法
    each()
    window.setTimeout() 和 window.setInterval() 使用说明
  • 原文地址:https://www.cnblogs.com/dream1024/p/13971779.html
Copyright © 2011-2022 走看看