冲刺NOIP2024专题之字符串专题
冲刺NOIP2024专题之字符串专题
Security
Yet Another LCP Problem
String Distance
观察这个定义,我们发现 \(f(x,y)\) 只可能等于 \(1,2,1337\)
且 \(1337\) 是很容易统计的,只要将所有字符串排序之后分成几个连通块(即通过操作可以相同的部分),不同连通块显然就是 \(1337\) ,只需计算每个连通块内值
我们发现只有当一个串内有一个极大不降子串(即这个不降子串不能向前后扩展),且除了这部分之外两个串都一样,\(f(x,y)\) 才为 \(1\) ,考虑统计这样的个数,而且由于极大的存在,这样统计不会算重复
我们发现这个玩意相当于枚举出每个极大不降子串之后找除了这一段之外与它的前后缀都相等的串,前缀好说,排序之后暴力跑 \(lcp\) 之后用个什么玩意维护一下一段里最小值就完了
后缀怎么办呢?它相当于翻转串的前缀,我们发现这玩意 \(trie\) 树貌似可以维护,首先倒着建一棵 \(trie\) 树,跑出前缀符合的插到它的串末尾,这就变成了动态在树上插个权值,给定一个点,查询这个点子树权值和,这玩意跑一遍dfs序用树状数组维护一下随便做,所以我们预处理出来极大不降子串,枚举每个起始位置,这个前缀符合的是单调的,相当于双指针,后缀直接查询即可(要判一下极大),之后为了保证查询复杂度,要记录每个字符在树上对应的节点
最后提醒一句,多测不清空,爆零两行泪
最坏复杂度 $ O(|S| \log |S|) $ 的,跑得飞快,xrlong
貌似有建两棵 \(trie\) 树二维偏序的做法,复杂度好像是一样的,大家快去模他
CODE
#include<bits/stdc++.h>
using namespace std;
#define N 200100
#define lowbit(a) a&(-a)
long long n,siz,last=1,ans;
char in[N];
vector <long long> s[N],us[N],d1[N],d2[N],d3[N],t(2*N),lcp(N);
struct TRIE
{
long long sizt=0,nxt[N][26],lar[N],inn[N],ss=0;
void del(int now)
{
lar[now]=inn[now]=0;
for(int i=0;i<=25;i++)
if(nxt[now][i]!=0)
del(nxt[now][i]),nxt[now][i]=0;
}
void clear()
{
del(0);
sizt=ss=0;
}
void in(int pl,int x)
{
for(int i=pl;i<=ss;i+=lowbit(i))
t[i]+=x;
}
long long check(int st,int ed)
{
long long sum1=0,sum2=0;
for(int i=ed;i>=1;i-=lowbit(i))
sum1+=t[i];
for(int i=st-1;i>=1;i-=lowbit(i))
sum2+=t[i];
return sum1-sum2;
}
void insert(const vector<long long> &ins,vector<long long> &rec)
{
long long now=0;
for(int i=siz;i>=1;i--)
{
if(nxt[now][ins[i]-'a']!=0)
now=nxt[now][ins[i]-'a'];
else
nxt[now][ins[i]-'a']=++sizt,now=sizt;
rec[i]=now;
}
}
void dfs(int now)
{
inn[now]=++ss;
lar[now]=1;
for(int i=0;i<=25;i++)
if(nxt[now][i]!=0)
{
dfs(nxt[now][i]);
lar[now]+=lar[nxt[now][i]];
}
}
long long find(long long node){return check(inn[node],inn[node]+lar[node]-1);}
}tree;
bool judge(const vector<long long> &cmp1,const vector<long long> &cmp2)
{
for(int i=1;i<=siz;i++)
if(cmp1[i]!=cmp2[i])
return false;
return true;
}
void solve(int beg,int end)
{
ans+=(end-beg+1)*(n-end)*1337;
long long size=0,tot=0;int now=0;tree.clear();
for(int i=beg;i<=end;i++)
d1[++size]=s[us[i][siz+1]];
vector<long long>().swap(lcp);
lcp.resize(size+1);
for(int i=1;i<=size;i++) vector<long long>().swap(d2[i]),vector<long long>().swap(d3[i]),d2[i].resize(siz+3),d3[i].resize(siz+3);
sort(d1+1,d1+size+1);
for(int i=1;i<=size;i++)
{
for(int j=siz,nxt=siz;j>=1;j--)
{
if(d1[i][j]>d1[i][j+1]) nxt=j;
d3[i][j]=nxt;
}
d3[i][0]=d3[i][1];
}
for(int i=2;i<=size;i++)
for(int j=1;j<=siz;j++)
if(d1[i-1][j]==d1[i][j]) lcp[i]=j;
else break;
for(int i=1;i<=size;i++) tree.insert(d1[i],d2[i]);
tree.dfs(0);
for(int i=0;i<=siz;i++)
{
now=0;
for(int j=1;j<=size;j++)
{
if(now>=j) tree.in(tree.inn[d2[j][1]],-1);
now=max(now,j);
while(now+1<=size&&lcp[now+1]>=i-1) now++,tree.in(tree.inn[d2[now][1]],1);
if(i==0||d1[j][i]<d1[j][i-1])
{
if(d3[j][i]+1<=siz)
tot+=tree.find(d2[j][d3[j][i]+1]);
else
tot+=tree.find(0);
}
}
}
ans+=size*(size-1)-tot;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
cin>>(in+1);
siz=strlen(in+1);
s[i].push_back(0);
for(int j=1;j<=siz;j++) s[i].push_back(in[j]);
s[i].push_back(i);
}
for(int i=1;i<=n;i++) us[i]=s[i];
for(int i=1;i<=n;i++) sort(us[i].begin(),us[i].end()-1);
sort(us+1,us+n+1);
for(int i=1;i<n;i++)
if(!judge(us[i],us[i+1]))
solve(last,i),last=i+1;
solve(last,n);
printf("%lld\n",ans);
return 0;
}