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,对节点字典序排序)相关的知识,希望对你有一定的参考价值。
没有题解真难受啊,让我来写第一篇吧…
对这 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} lu−lfau个不同的子串,这些子串的公共后缀长度是 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(平面几何)