BZOJ3998 [TJOI2015]弦论(后缀自动机求字典序第k小子串)

题目链接

先考虑相同子串算不同的情况

性质一:后缀自动机中parent树中一个节点子树中np类节点的个数为该串在原串中出现的次数

根据这个性质可以通过一遍parent树上的dfs求出每个点的出现次数

后缀自动机里每个节点代表的串是多个的,但是这些串出现的次数都是一样的。

因此我们沿着字符边走到一个点的时候,这个点的权值就是这个字符串的出现次数

此时可以考虑到和权值线段树找第k大的思路:树上二分

记录一个点的新值sum来表示这个点能到的所有字符串的数量,然后可以通过一遍DAGdp来跑出sum

查找的话就是每次从小往大枚举该点的每一个字符边,如果到此为止的字符串数总和刚好大于k,那么答案就在这个点控制的子图里,如果刚好在这个点出现次数内,那么就到这个点为止

我用了比较烦的方法记录边,但其实没有必要,可以边查找边输出

再考虑重复只算一次的情况

性质二:后缀自动机表达的字符串为所有不重不漏的子串

根据这个性质,我们只要不要对parent树进行dfs,把每个点的出现次数直接设成1再跑上述的树上二分就行了

代码如下(太丑了)

#include
#define N 1000050
using namespace std;

struct SAM
{
	struct point
	{
		int son[26],fa,len,at,mx,vis[26];
	}t[N];
	
	int last=1,cnt=1;
	long long sz[N],sum[N];
	vector<int> g[1000050]; 
	bool vis[1000050];
	
	void add(int c)
	{
		int p=last;
		int np=++cnt;
		t[np].len=t[p].len+1;
		sz[np]=1;
		while(p&&(!t[p].son[c]))
		{
			t[p].son[c]=np;
			p=t[p].fa;
		}
		if(!p) t[np].fa=1;
		else
		{
			int q=t[p].son[c],nq;
			if(t[q].len==t[p].len+1)
			{
				t[np].fa=q;
			}
			else
			{
				nq=++cnt;
				t[nq]=t[q];
				t[nq].len=t[p].len+1;
				t[q].fa=t[np].fa=nq;   //!!!
				while(p&&(t[p].son[c]==q))
				{
					t[p].son[c]=nq;
					p=t[p].fa;
				}
			}
		}
		last=np;
	}
	
	void dfs(int now)
	{
		for(int i=0;i<g[now].size();i++)
		{
			dfs(g[now][i]);
		}
		sz[t[now].fa]+=sz[now];
	}
	
	void dfs1(int now)
	{
		vis[now]=1;
		sum[now]=sz[now];
		for(int i=0;i<26;i++)
		{
			if(t[now].son[i])
			{
				if(!vis[t[now].son[i]]) dfs1(t[now].son[i]);
				sum[now]+=sum[t[now].son[i]]; 
			}
		}
	}
	
	void dfs2(int now,int k)
	{
		gg++;
		long long tot=0ll;
		for(int i=0;i<26;i++)
		{
			register int son=t[now].son[i];
			if(son)
			{
				if(k<=tot+sz[son])
				{
					t[now].vis[i]=1;
					return;
				} 
				if(k<=tot+sum[son])
				{
					t[now].vis[i]=1;
					dfs2(son,k-tot-sz[son]);
					return;
				}
				tot+=sum[son];
			} 
		}
	}
	
	void print(int now)
	{
		for(int i=0;i<26;i++)
		{
			if(t[now].vis[i])
			{
				printf("%c",(char)('a'+i));
				print(t[now].son[i]);
			}
		}
	}
	
	int solve(int kd,int k)
	{
		for(int i=1;i<=cnt;i++) g[t[i].fa].push_back(i); 
		if(kd) dfs(1);
		else for(int i=1;i<=cnt;i++) sz[i]=1;
		sz[1]=0;
		dfs1(1);
		if(k>sum[1]) return 0;
		dfs2(1,k); 
		print(1);
		return 1;
	}
}sam;

char s[500050];

int main()
{
	int k,kd;
	scanf("%s",s+1);
	scanf("%d%d",&kd,&k);
	register int len=strlen(s+1);
	for(int i=1;i<=len;i++)
	{
		sam.add(s[i]-'a');
	} 
	if(!sam.solve(kd,k))
	{
		puts("-1");
	}	
} 

你可能感兴趣的:(后缀自动机,bzoj)