zoukankan      html  css  js  c++  java
  • HackerRank Special Substrings 回文树+后缀自动机+set

    传送门

    既然要求对每个前缀都求出答案,不难想到应该用回文树求出所有本质不同的回文子串。

    然后考虑如何对这些回文子串的前缀进行去重。

    结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的LCP长度之和。

    这个结论其实不难理解。可以回忆后缀数组经典题目:求一个字符串本质不同的子串个数。道理是一样的。

    然后就有思路了,从空串开始每次加一个字符,用一个set维护当前所有本质不同的回文子串(只存左右端点),如果产生了新的回文子串就扔进set里跟前驱后继xjb更新一下答案。

    字典序比较用后缀数组会比较方便。然而我不会写后缀数组,那就后缀自动机求LCP硬上好了。注意由于这题只需要考虑回文子串,所以正着反着都是一样的,这么一来也就不必对反串建后缀自动机,直接对原串建个自动机然后求最长公共后缀就行了。

    注意这题用LCP比较字典序的时候需要对LCP是否超出串长进行特判,细节虽然不多但比较容易忽视。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<set>
    #include<vector>
    using namespace std;
    const int maxn=300005;
    namespace SAM{
    	int root,last,cnt=0,val[maxn<<1]={0},par[maxn<<1]={0},go[maxn<<1][26]={0};
    	vector<int>G[maxn<<1];
    	int id[maxn<<1],tim=0,rnk[maxn],f[maxn][19],log_tbl[maxn];
    	void initalize();
    	void extend(int,int);
    	void dfs(int);
    	int LCP(int,int);
    }
    using SAM::LCP;
    namespace PAM{
    	int last,cnt,go[maxn][26],val[maxn],par[maxn];
    	int extend(int);
    }
    struct A{
    	int l,r;
    	A(int l,int r):l(l),r(r){}
    	bool operator<(const A &a)const;
    };
    char s[maxn];
    int n;
    int main(){
    	scanf("%d",&n);
    	scanf("%s",s+1);
    	SAM::initalize();
    	PAM::par[0]=PAM::cnt=1;
    	PAM::val[1]=-1;
    	set<A>DS;
    	long long ans=0;
    	for(int i=1;i<=n;i++){
    		int l=PAM::extend(i);
    		if(l){
    			set<A>::iterator u=DS.lower_bound(A(l,i)),v=u;
    			if(u==DS.end()){
    				if(!DS.empty())u=DS.find(*DS.rbegin());
    			}
    			else{
    				if(u!=DS.begin())u--;
    				else u=DS.end();
    			}
    			if(u!=DS.end()&&v!=DS.end())
    				ans+=min(min(u->r-u->l,v->r-v->l)+1,LCP(u->r,v->r));
    			ans+=i-l+1;
    			if(u!=DS.end())ans-=min(min(i-l,u->r-u->l)+1,LCP(i,u->r));
    			if(v!=DS.end())ans-=min(min(i-l,v->r-v->l)+1,LCP(i,v->r));
    			DS.insert(A(l,i));
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    namespace SAM{
    	void initalize(){
    		root=last=cnt=1;
    		for(int i=1;i<=n;i++)extend(s[i]-'a',i);
    		for(int i=2;i<=cnt;i++)G[par[i]].push_back(i);
    		dfs(1);
    		log_tbl[0]=-1;
    		for(int i=1;i<=n;i++)log_tbl[i]=log_tbl[i>>1]+1;
    		for(int j=1;(1<<j)<n;j++)
    			for(int i=1;i+(1<<j)-1<n;i++)
    				f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    	}
    	void extend(int c,int v){
    		int p=last,np=++cnt;
    		val[np]=val[p]+1;
    		while(p&&!go[p][c]){
    			go[p][c]=np;
    			p=par[p];
    		}
    		if(!p)par[np]=root;
    		else{
    			int q=go[p][c];
    			if(val[q]==val[p]+1)par[np]=q;
    			else{
    				int nq=++cnt;
    				val[nq]=val[p]+1;
    				memcpy(go[nq],go[q],sizeof(go[q]));
    				par[nq]=par[q];
    				par[np]=par[q]=nq;
    				while(p&&go[p][c]==q){
    					go[p][c]=nq;
    					p=par[p];
    				}
    			}
    		}
    		id[np]=v;
    		last=np;
    	}
    	void dfs(int x){
    		if(id[x]){
    			if(tim)f[tim][0]=val[last];
    			rnk[id[x]]=++tim;
    			last=x;
    		}
    		for(int i=0;i<(int)G[x].size();i++)dfs(G[x][i]);
    		last=par[x];
    	}
    	int LCP(int l,int r){
    		if(l==r)return l;
    		l=rnk[l];
    		r=rnk[r];
    		if(l>r)swap(l,r);
    		r--;
    		int k=log_tbl[r-l+1];
    		return min(f[l][k],f[r-(1<<k)+1][k]);
    	}
    }
    namespace PAM{
    	int extend(int i){
    		int p=last,c=s[i]-'a';
    		while(s[i]!=s[i-val[p]-1])p=par[p];
    		if(!go[p][c]){
    			int q=++cnt,now=p;
    			val[q]=val[p]+2;
    			do p=par[p];while(s[i]!=s[i-val[p]-1]);
    			par[q]=go[p][c];
    			last=go[now][c]=q;
    			return i-val[q]+1;
    		}
    		else last=go[p][c];
    		return 0;
    	}
    }
    bool A::operator<(const A &a)const{
    	if(r==a.r)return false;
    	int t=LCP(r,a.r);
    	if(t>r-l+1&&t>a.r-a.l+1)return r-l+1>a.r-a.l+1;
    	if(t>r-l+1)return true;
    	if(t>a.r-a.l+1)return false;
    	return s[r-t]<s[a.r-t];
    }
    
  • 相关阅读:
    [Linux]Ubuntu下正确挂载NTFS磁盘的方法
    Google搜索指令与自定义引擎
    【Linux】Android手机在Ubuntu上无法被adb识别解决办法(权限相关)
    [ Linux ] Remove PPA source from your pc
    一键去除 UC浏览器 论坛模式 内置的广告
    Huawei U8825d 对4G手机内存重新分区过程[把2Gb内置SD卡容量划分给DATA分区使用]
    【Android】把Linux GCC安插在Android手机上
    [Windows]隐藏文件及目录的命令
    Linux压缩包简体中文乱解决方案[全]
    修改su密码 macbook
  • 原文地址:https://www.cnblogs.com/hzoier/p/9240746.html
Copyright © 2011-2022 走看看