BZOJ2097: [Usaco2010 Dec]Exercise 奶牛健美操 二分答案+贪心+DP
BZOJ4216: Pig 恶心题+前缀和

BZOJ3998: [TJOI2015]弦论 后缀自动机

shinbokuow posted @ Sep 02, 2015 10:42:09 AM in BZOJ with tags 后缀树 后缀自动机 , 1338 阅读

 

题解:

先说明,这份代码很不幸的超(ka)时(chang)了。

借这道题目的机会再重新说明一下后缀自动机&后缀树吧。

发现最近一段时间很是习惯于利用后缀树思考问题了,但事实上大概后缀自动机不仅仅是逆序后缀树而已吧。

考虑后缀树,定义$siz_i$表示节点$i$的子树內存在多少个结束节点(即后缀)。

那么对于每个节点$i$,都实际上对应了$len_i-len_{pa_i}$个字符串,长度是从$len_{pa_i}+1-len_i$连续的。

同时这些字符串都会出现$siz_i$次。

为了得到这条边上的字符串,我们需要找出这个节点子树内的任意一个后缀的位置,并利用$len_i$以及$len_{pa_i}$来得到这个节点的所有串。

考虑一个点的若干条儿子边,边代表的字符串的首字母必定不同,我们用首字母字典序大小来决定遍历顺序的先后。

如果想求第$k$小的串,我们就在后缀树上dfs,最终得到是一条边上的第几个串,然后就可以了。

后缀自动机是逆序后缀树,自动机体现在后缀树上的trans指针,即支持在一个字符串上在开头添加一个字符得到新的节点的指针。

由于是反串的后缀树,那么在开头添加一个字符相当于在原串的末尾添加字符。

从根开始,通过若干trans指针会得到一条路径,对于不同字符串,即使路径的最终节点相同,但是路径本身不同。

换言之,我们可以将子串的询问问题转化为拓扑图的路径统计问题。

上面是随便说的。。。

总之,以后考虑字符串的问题时可以从两种方面来考虑:

(1)树结构的后缀树

(2)拓扑图结构的后缀自动机

两种结构各有其优点,并不能像我之前一样完全否定后者。

代码:

#include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstdlib>
using namespace std;

typedef long long ll;
#define N 500010
int tr[N<<1][26],len[N<<1],pa[N<<1],cnt,root,last,siz[N<<1];
ll f[N<<1],g[N<<1];
vector<int>v[N<<1];

inline int newnode(int _len){
	len[++cnt]=_len;
	return cnt;
}

char s[N];
int in[N<<1];
int q[N<<1],fr,ta;
int temp[N<<1];

char ans[N];
int top;

int main(){
	//clock_t begin=clock();
	//freopen("tt.in","r",stdin);
	//freopen("tt.out","w",stdout);
	int n,i,j;
	scanf("%s",s+1);
	n=strlen(s+1);
	int y,p,q,np,nq;
	root=last=newnode(0);
	for(i=1;i<=n;++i){
		np=newnode(len[last]+1);
		for(y=s[i]-'a',p=last;p&&!tr[p][y];p=pa[p])
			tr[p][y]=np;
		if(!p)
			pa[np]=root;
		else{
			if(len[q=tr[p][y]]==len[p]+1)
				pa[np]=q;
			else{
				nq=newnode(len[p]+1);
				pa[nq]=pa[q];
				pa[q]=pa[np]=nq;
				memcpy(tr[nq],tr[q],sizeof tr[q]);
				for(;p&&tr[p][y]==q;p=pa[p])
					tr[p][y]=nq;
			}
		}
		siz[last=np]=1;
	}
	for(i=2;i<=cnt;++i)
		++in[pa[i]];
	for(i=1;i<=cnt;++i)
		if(!in[i])
			::q[ta++]=i;
	while(fr!=ta){
		i=::q[fr++];
		if(pa[i]){
			siz[pa[i]]+=siz[i];
			if(!--in[pa[i]])
				::q[ta++]=pa[i];
		}
	}
	for(i=1;i<=cnt;++i)
		for(j=0;j<26;++j)
			if(tr[i][j]){
				v[tr[i][j]].push_back(i);
				++in[i];
			}
	for(fr=ta=0,i=1;i<=cnt;++i)
		if(!in[i])
			::q[ta++]=i;
	while(fr!=ta){
		i=::q[fr++];
		if(i!=root){
			++f[i];
			g[i]+=siz[i];
		}
		for(j=0;j<v[i].size();++j){
			f[v[i][j]]+=f[i];
			g[v[i][j]]+=g[i];
			if(!--in[v[i][j]])
				::q[ta++]=v[i][j];
		}
	}
	//cout<<f[root]<<endl;
	//cout<<g[root]<<endl;
	int type,k;
	cin>>type>>k;
	if(type==0){
		for(i=2;i<=cnt;++i)
			temp[i]=1;
	}
	else{
		for(i=2;i<=cnt;++i)
			temp[i]=siz[i];
		for(i=1;i<=cnt;++i)
			f[i]=g[i];
	}
	if(k>f[root])
		puts("-1");
	else{
		p=root;
		while(k>0){
			for(int i=0;i<26;++i){
				if(tr[p][i]){
					if(k>f[tr[p][i]])
						k-=f[tr[p][i]];
					else{
						ans[top++]='a'+i;
						k-=temp[p=tr[p][i]];
						break;
					}
				}
			}
		}
	}
	puts(ans);
	//puts("");
	//printf("%ldms",clock()-begin);
	return 0; 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter