The 15th Chinese Northeast L. k-th Smallest Common Substring(广义SAM,对节点字典序排序)

Posted issue是fw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了The 15th Chinese Northeast L. k-th Smallest Common Substring(广义SAM,对节点字典序排序)相关的知识,希望对你有一定的参考价值。

LINK

没有题解真难受啊,让我来写第一篇吧…

对这 n n n个串建广义后缀自动机,然后对每个节点进行染色

染色方法类似这题这题

然后我们就知道了自动机上哪些节点是 n n n个串公共的

现在要求第 k k k小子串,显然可以按照弦论的方式,在自动机的 D A G DAG DAG从小试到大

然而这题有 q q q次询问,这么暴力找的方法已经不适用了.考虑优化

S A M SAM SAM的一个 t r i c k trick trick

自动机有个很好的性质,就是节点 u u u包含 l u − l f a u l_u-l_{fa_u} lulfau个不同的子串,这些子串的公共后缀长度是 l f a i l_{fa_i} lfai

这意味着,如果 u u u存在两个儿子 v 1 , v 2 v_1,v_2 v1,v2,那么 v 1 , v 2 v_1,v_2 v1,v2代表的串集合的反串字典序有明显的大小关系

因为 v 1 , v 2 v_1,v_2 v1,v2的长度 l f a u l_{fa_u} lfau的后缀相同,也就是 v 1 , v 2 v_1,v_2 v1,v2的反串的长度为 l f a u l_{fa_u} lfau的前缀相同

并且在反串的 l f a u + 1 l_{fa_u}+1 lfau+1处不同,所以才会分裂成两个不同的儿子 v 1 , v 2 v_1,v_2 v1,v2

显然,我们可以按照反串的 l f a u + 1 l_{fa_u}+1 lfau+1这个位置的字母大小排序,那么 u u u的儿子就按照从小到大的字典序排序好了

对于 p a r e n t parent parent树上每个节点都这样排序后,再去按顺序遍历 p a r e n t parent parent树,我们得到的节点顺序就是从小到大的(反串字典序)

实现

在这题中,我们已经得到哪些节点是 n n n个串的公共串了,所以需要按照字典序给他们排序

由于上面的排序方式是对反串的字典序而言,所以我们把所有串先取反串

然后记录每个节点的一个 e n d p o s endpos endpos(记录最大 e n d p o s endpos endpos)以及属于哪个串(记录最小串).

(当然 e n d p o s endpos endpos可能有多个,节点也可以属于多个串,不过我们的目的只是为了得到反串的 l f a u + 1 l_{fa_u}+1 lfau+1位置是什么字母才记录的这个信息,所以无所谓)

但是 e n d p o s endpos endpos必须记录最大值,因为最后输出答案需要使用 e n d p o s endpos endpos,在反串越大在正串越小,所以记录最大的

于是我们把这些节点按照字典序排序了,每个节点含有的子串数也已知,做一遍前缀和

询问 k k k的时候,二分到大于等于 k k k的那个节点输出即可.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 6e5+10;
int n;
string a[maxn];
int zi[maxn][30],fa[maxn],l[maxn],id=1,las=1;
int vis[maxn],color[maxn];
struct p
{
    int fi,se;
    bool operator<(const p& a)const
	{
        return fi==a.fi?se>a.se:fi<a.fi;
    }
}pos[maxn];
void insert(int c,int col, p w)
{
	int p = las, np = ++id;  las = id;
	l[np] = l[p]+1; 
	pos[np] = min( pos[np],w );
	for( ; p && zi[p][c]==0 ;p=fa[p] )	zi[p][c] = np;
	if( p==0 )	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( l[q]==l[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id;
			fa[nq] = fa[q], l[nq] = l[p]+1;
			color[nq] = color[q], vis[nq] = vis[q]; pos[nq] = min( w,pos[q] );
			memcpy( zi[nq],zi[q],sizeof zi[q] );
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q ;p=fa[p] )	zi[p][c] = nq;
		}
	}
	while( np && color[np]!=col )
		color[np] = col, vis[np]++, np = fa[np];
}
vector<int>vec[maxn],ed,LE;
vector<ll>sum;
void updpos(int u)
{
	for(auto v:vec[u] )
	{
		updpos( v );
		pos[u] = min( pos[u],pos[v] );
	}
	sort( vec[u].begin(),vec[u].end(),[&](int q,int w)
		{
			return a[pos[q].fi][pos[q].se-l[u]]<a[pos[w].fi][pos[w].se-l[u]];	
		});
}
void getorder(int u)
{
	if( u!=1 && vis[u]==n )
	{
		sum.push_back( l[u]-l[fa[u]] );
		ed.push_back( pos[u].se );
		LE.push_back( l[fa[u]] );
	}
	for(auto v:vec[u] )	getorder( v );
}
void read()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		reverse( a[i].begin(),a[i].end() );
		int le = a[i].length();
		for(int j=0;j<le;j++)	insert( a[i][j]-'a',i,p{i,j} );
		las = 1;
	}
	for(int i=2;i<=id;i++)	vec[fa[i]].push_back( i );
	updpos(1); getorder(1);
	for(int i=1;i<sum.size();i++)	sum[i] += sum[i-1];
}
int ff;
void solve(int k)
{
	if( k>sum.back() )	cout << -1 << endl;
	else
	{
		int p = lower_bound( sum.begin(),sum.end(),k )-sum.begin();
		int l = a[1].length()-1-ed[p];
		int r = l+LE[p]+( k-(p?sum[p-1]:0));
		cout << l << " " << r << '\\n';
	}
}
void init()
{
	sum.clear(); ed.clear(); LE.clear(); 
	for(int i=1;i<=id;i++)
	{
		memset( zi[i],0,sizeof zi[i] );
		l[i] = fa[i以上是关于The 15th Chinese Northeast L. k-th Smallest Common Substring(广义SAM,对节点字典序排序)的主要内容,如果未能解决你的问题,请参考以下文章

The 15th Chinese Northeast Collegiate C. Vertex Deletion(树形dp)

The 15th Chinese Northeast L. k-th Smallest Common Substring(广义SAM,对节点字典序排序)

The 15th Chinese Northeast Collegiate Programming Contest C. Vertex Deletion (树形dp)

The 13th Chinese Northeast Contest H. Skyscraper(差分+树状数组)

The 13th Chinese Northeast Contest B. Balanced Diet(前缀和)

The 13th Chinese Northeast Contest C. Line-line Intersection(平面几何)