zoukankan      html  css  js  c++  java
  • 「TJOI / HEOI2016」字符串

    题目链接

    问题分析

    看见字符串,还涉及到子串和前缀的,当然就想到了SAM(其实我不会SA)。

    首先我们把串反一下,就变成了求S[a..b]的所有子串与S[c..d]的最长后缀的长度。

    我们把问题说成这样:求是S[a..b]子串的S[c..d]的最长后缀长度。于是我们就可以二分答案,然后判定是否在S[a..b]中出现就可以啦!

    考虑二分答案后如何判定

    假设我们现在判断Len是否可行。我们首先找到一个SAM上的状态,它的Right集合中含有d这个位置,并且Len介于这个状态的MinLen和MaxLen之间。那么只需要查询Right集合中有没有a+Len-1到b的元素即可。

    具体细节是这样的:首先我们应该在建SAM的时候直接保存d最早出现的状态。这个状态就是Right集合中含有d,并且在Parent树上深度最深的节点。然后对于一个长度Len,我们在Parent树上倍增,找到[MinLen..MaxLen]包含Len的节点,判断的后缀就包含在这个状态里。同时,如果这个状态的Right集合中有[a+Len-1..b]的元素,那么说明这个串也存在于[a..b]中,否则就没有。

    所以我们需要线段树合并求Right集合。然后就做完了!

    时间复杂度(O(nlog^2n))

    参考程序

    我不知道为什么写的这么翔

    #include <bits/stdc++.h>
    //#ifndef DEBUG
    //	#define DEBUG
    //#endif
    using namespace std;
    
    const int Maxn = 100010;
    const int MaxLog = 20;
    int n, m;
    char Ch[ Maxn ];
    int Index[ Maxn ];
    
    namespace Seg { //线段树
    	struct segmentTree {
    		segmentTree *LeftChild, *RightChild;
    		int Num;
    		void Init() {
    			Num = 0;
    			LeftChild = RightChild = NULL;
    		}
    	};
    	segmentTree *SegmentTree[ Maxn << 1 ];
    
    	void Init() {
    		for( int i = 0; i < Maxn << 1; ++i )
    			SegmentTree[ i ] = NULL;
    		return;
    	}
    
    	segmentTree *Insert( segmentTree *Index, int Left, int Right, int Key ) {
    		if( Index == NULL ) {
    			Index = new segmentTree;
    			Index -> Init();
    		}
    		++Index -> Num;
    		if( Left == Right ) return Index;
    		int Mid = ( Left + Right ) >> 1;
    		if( Key <= Mid ) Index -> LeftChild = Insert( Index -> LeftChild, Left, Mid, Key );
    		if( Key > Mid ) Index -> RightChild = Insert( Index -> RightChild, Mid + 1, Right, Key );
    		return Index;
    	}
    
    	void Insert( int Pos, int Key ) {
    #ifdef DEBUG
    		printf( "Inform(Seg) : Add [ %d ] -> [ %d ]
    ", Key, Pos );
    #endif
    		SegmentTree[ Pos ] = Insert( SegmentTree[ Pos ], 1, n, Key );
    		return;
    	}
    
    	segmentTree *Merge( segmentTree *x, segmentTree *y, int Left, int Right ) {
    		if( x == NULL && y == NULL ) return x;
    		if( x != NULL && y == NULL ) return x;
    		segmentTree *z = new segmentTree;//注意要新建节点
    		if( x == NULL && y != NULL ) {
    			*z = *y;
    			return z;
    		}
    		*z = *x;
    		z -> Num += y -> Num;
    		if( Left == Right ) return z;
    		int Mid = ( Left + Right ) >> 1;
    		z -> LeftChild = Merge( z -> LeftChild, y -> LeftChild, Left, Mid );
    		z -> RightChild = Merge( z -> RightChild, y -> RightChild, Mid + 1, Right );
    		return z;
    	}//线段树合并求Right集合
    
    	void Play( segmentTree *Index, int Left, int Right ) {
    		if( Index == NULL ) return;
    		if( Index -> Num == 0 ) return;
    		if( Left == Right ) {
    			printf( "%d ", Left );
    			return;
    		}
    		int Mid = ( Left + Right ) >> 1;
    		Play( Index -> LeftChild, Left, Mid );
    		Play( Index -> RightChild, Mid + 1, Right );
    		return;
    	}//Debug用
    
    	void Merge( int x, int y ) {
    #ifdef DEBUG
    		printf( "Inform(Seg) : Merge %d <-- %d
    ", x, y );
    #endif
    		SegmentTree[ x ] = Merge( SegmentTree[ x ], SegmentTree[ y ], 1, n );
    #ifdef DEBUG
    		printf( "  Right : " ); Play( SegmentTree[ x ], 1, n ); printf( "
    " );
    #endif
    		return;
    	}//这是一个接口
    
    	int Count( segmentTree *Index, int Left, int Right, int L, int R ) {
    		if( Index == NULL ) return 0;
    		if( L <= Left && Right <= R ) return Index -> Num;
    		int Mid = ( Left + Right ) >> 1;
    		int Ans = 0;
    		if( L <= Mid ) Ans += Count( Index -> LeftChild, Left, Mid, L, R );
    		if( R > Mid ) Ans += Count( Index -> RightChild, Mid + 1, Right, L, R );
    		return Ans;
    	}//求一段区间内的元素个数,用于判断区间内是否存在元素
    } //Seg
    
    
    namespace SAM {
    	int Now, Used = 0, Last;
    	struct suffixAutomaton {
    		int Parent, Child[ 26 ], Len, MinLen;
    	};
    	suffixAutomaton SuffixAutomaton[ Maxn << 1 ];
    
    	void Init() {
    		Used = 0;
    		memset( SuffixAutomaton, 0, sizeof( SuffixAutomaton ) );
    		++Used; Last = Used;
    		return;
    	}
    
    	void Insert( int t ) {
    		Now = ++Used;
    		SuffixAutomaton[ Now ].Len = SuffixAutomaton[ Last ].Len + 1;
    		int p = Last;
    		for( ; p && !SuffixAutomaton[ p ].Child[ t ]; p = SuffixAutomaton[ p ].Parent )
    			SuffixAutomaton[ p ].Child[ t ] = Now;
    		Last = Now;
    		if( !p ) {
    			SuffixAutomaton[ Now ].Parent = 1;
    			return;
    		}
    		int q = SuffixAutomaton[ p ].Child[ t ];
    		if( SuffixAutomaton[ p ].Len + 1 == SuffixAutomaton[ q ].Len ) {
    			SuffixAutomaton[ Now ].Parent = q;
    			return;
    		}
    		int Clone = ++Used;
    		SuffixAutomaton[ Clone ].Len = SuffixAutomaton[ p ].Len + 1;
    		SuffixAutomaton[ Clone ].Parent = SuffixAutomaton[ q ].Parent;
    		for( int i = 0; i < 26; ++i )
    			SuffixAutomaton[ Clone ].Child[ i ] = SuffixAutomaton[ q ].Child[ i ];
    		for( ; p && SuffixAutomaton[ p ].Child[ t ] == q; p = SuffixAutomaton[ p ].Parent )
    			SuffixAutomaton[ p ].Child[ t ] = Clone;
    		SuffixAutomaton[ Now ].Parent = SuffixAutomaton[ q ].Parent = Clone;
    		return;
    	}
    } //SAM
    
    namespace Tree {//维护Parent树
    	struct edge {
    		int To, Next;
    	};
    	edge Edge[ Maxn << 2 ];
    	int Start[ Maxn << 1 ], Used = 0;
    	int Deep[ Maxn << 1 ], D[ Maxn << 1 ][ MaxLog ];//倍增需要的数组
    
    	inline void AddEdge( int x, int y ) {
    #ifdef DEBUG
    		printf( "Inform(Tree) : Link %d <---> %d
    ", x, y );
    #endif
    		Edge[ ++Used ] = ( edge ) { y, Start[ x ] };
    		Start[ x ] = Used;
    		return;
    	}
    
    	void Build( int Index, int Father ) {//建树,顺便求出MinLen
    		D[ Index ][ 0 ] = Father;
    		Deep[ Index ] = Deep[ Father ] + 1;
    		if( Index > 1 )
    			SAM::SuffixAutomaton[ Index ].MinLen = SAM::SuffixAutomaton[ Father ].Len + 1;
    		for( int i = 1; i < MaxLog; ++i ) 
    			D[ Index ][ i ] = D[ D[ Index ][ i - 1 ] ][ i - 1 ];
    		for( int t = Start[ Index ]; t; t = Edge[ t ].Next ) {
    			int v = Edge[ t ].To;
    			if( v == Father ) continue;
    			Build( v, Index );
    		}
    		return;
    	}
    
    	void Init() {
    		for( int i = 2; i <= SAM::Used; ++i ) {
    			AddEdge( SAM::SuffixAutomaton[ i ].Parent, i );
    			AddEdge( i, SAM::SuffixAutomaton[ i ].Parent );
    		}
    		Build( 1, 1 );
    		return;
    	}
    
    	void Union( int Index, int Father ) {
    		for( int t = Start[ Index ]; t; t = Edge[ t ].Next ) {
    			int v = Edge[ t ].To;
    			if( v == Father) continue;
    			Union( v, Index );
    			Seg::Merge( Index, v );
    		}
    		return;
    	}//求Right集合
    
    	void Union() {
    		Union( 1, 1 );
    		return;
    	}//这是一个接口
    
    	void Play( int Index, int Father ) {
    		printf( "Index = %d, Father = %d
    ", Index, Father );
    		printf( "  Min = %d, Max = %d
    ", SAM::SuffixAutomaton[ Index ].MinLen, SAM::SuffixAutomaton[ Index ].Len );
    		printf( "  Right : " );
    		Seg::Play( Seg::SegmentTree[ Index ], 1, n );
    		printf( "
    " );
    		for( int t = Start[ Index ]; t; t = Edge[ t ].Next ) {
    			int v = Edge[ t ].To;
    			if( v == Father ) continue;
    			Play( v, Index );
    		}
    		return;
    	}//Debug用
    } //Tree
    
    void Debug() {
    	printf( "Total Used Point in SAM : %d
    ", SAM::Used );
    	Tree::Play( 1, 0 );
    	printf( "Start : " );
    	for( int i = 1; i <= n; ++i ) printf( "%d ", Index[ i ] );
    	printf( "
    
    
    " );
    	system( "pause" );
    	return;
    }
    
    void Read() {
    	scanf( "%d%d", &n, &m );
    	scanf( "%s", Ch + 1 );
    	for( int i = 1; i + i <= n; ++i ) 
    		swap( Ch[ i ], Ch[ n - i + 1 ] );
    #ifdef DEBUG
    	printf( "%d %d
    ", n, m );
    	printf( "%s
    ", Ch + 1 );
    	system( "pause" );
    #endif
    	return;
    }
    
    void Build() {
    	Seg::Init();
    	SAM::Init();
    	for( int i = 1; i <= n; ++i ) {
    		SAM::Insert( Ch[ i ] - 'a' );
    		Index[ i ] = SAM::Now;//存下Right集合包含位置i并且在Parent树中深度最深的点
    		Seg::Insert( SAM::Now, i );//插入位置i
    	}
    	Tree::Init();
    #ifdef DEBUG
    	Debug();
    #endif
    	Tree::Union();
    #ifdef DEBUG
    	Debug();
    #endif
    	return;
    }
    
    bool Check( int Len, int Index, int Left, int Right ) {
    #ifdef DEBUG
    	printf( "Inform(Check) : Len = %d, Left = %d, Right = %d, Index = %d
    ", Len, Left, Right, Index );
    	printf( "Inform(Check) : Min = %d, Max = %d
    ", SAM::SuffixAutomaton[ Index ].MinLen, SAM::SuffixAutomaton[ Index ].Len );
    #endif
    	if( !Len ) return true;
    	if( Len > SAM::SuffixAutomaton[ Index ].Len ) return false;
    	if( SAM::SuffixAutomaton[ Index ].MinLen <= Len && Len <= SAM::SuffixAutomaton[ Index ].Len ) {
    #ifdef DEBUG
    		printf( "Count = %d
    ", Seg::Count( Seg::SegmentTree[ Index ], 1, n, Left, Right ) );
    #endif
    		return Seg::Count( Seg::SegmentTree[ Index ], 1, n, Left, Right );
    	}
    	for( int i = MaxLog - 1; i >= 0; --i ) //倍增
    		if( SAM::SuffixAutomaton[ Tree::D[ Index ][ i ] ].MinLen > Len )
    			Index = Tree::D[ Index ][ i ];
    	Index = Tree::D[ Index ][ 0 ];
    #ifdef DEBUG
    	printf( "Inform(Check) : Min = %d, Max = %d
    ", SAM::SuffixAutomaton[ Index ].MinLen, SAM::SuffixAutomaton[ Index ].Len );
    	printf( "Count = %d
    ", Seg::Count( Seg::SegmentTree[ Index ], 1, n, Left, Right ) );
    #endif
    	return Seg::Count( Seg::SegmentTree[ Index ], 1, n, Left, Right );
    }
    
    void Solve() {
    	for( int i = 1; i <= m; ++i ) {
    		int a, b, c, d;
    		scanf( "%d%d%d%d", &a, &b, &c, &d );
    		a = n - a + 1; b = n - b + 1; c = n - c + 1; d = n - d + 1;
    		swap( a, b ); swap( c, d );
    		int Left = 0, Right = d - c + 1;
    		while( Left < Right ) {//二分答案
    			int Mid = ( Left + Right + 1 ) >> 1;
    			if( Check( Mid, Index[ d ], a + Mid - 1, b ) ) Left = Mid; else Right = Mid - 1;
    		}
    		printf( "%d
    ", Left );
    	}
    	return;
    }
    
    int main() {
    	Read();
    	Build();
    	Solve();
    	return 0;
    }
    
  • 相关阅读:
    uva 10129
    年化利率
    house买房原理,2019,第一版
    car二手车购买原理
    car购车翻译篇
    car配置篇
    健身原理
    语法学习,从句
    语法学习,简单语句
    名词解释
  • 原文地址:https://www.cnblogs.com/chy-2003/p/10512647.html
Copyright © 2011-2022 走看看