背单词
https://loj.ac/problem/2012
题目描述
给出\(n\)个字符串,他们有一定顺序,并且满足(设第\(i\)个字符串为\(s\)):
\(①\)若这\(n\)个字符串中有\(s\)的后缀,并且顺序在\(i\)之后,代价为\(n*n\)。
\(②\)若这\(n\)个字符串中不存在\(s\)的后缀,代价为\(i\)。
\(③\)若这\(n\)个字符串中存在\(s\)的后缀在\(i\)之前,并且假设最近的后缀位置为\(j\),代价为\(i-j\)。
思路
吐槽一下出题人的语文水平,看了半天没看出顺序是自己定的,后缀指的是\(n\)个字符串中存在的所有后缀。不过题目理清楚就比较简单了,首先第一条规则没有任何意义,因为如果按照\(②、③\)规则,显然可以存在并且代价小于\(n*n\),所以我们只用考虑把一个字符串的后缀填在它前面。接下来就是解决统计答案的问题。由于字符串和字符串的后缀存在图的关系,我们可以从后缀到字符串建一条有向边。所以我们可以\(dfs\)建成的图,从\(0\)点开始。所以对于一个位置\(u\),我们考虑对所有的子树的\(size\),进行一种排序,使父亲和子树的差最小。我们可以用贪心,显然\(size\)小的要排在后面,这样可以使总和最小。
由于我们有一个虚根\(0\),连向其他所有无后缀的节点,所以必定可以通过\(dfs\)访问所有节点。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=3e5+10;
int val[MAXN],nxt[MAXN<<1],to[MAXN<<1],head[MAXN];
int ch[510005][26],tot,idx,siz[MAXN],fa[MAXN],ed[MAXN];
char s[MAXN];
void add_edge(int x,int y)
{
nxt[++tot]=head[x];
head[x]=tot;
to[tot]=y;
}
ll ans=0;
vector<int>a[MAXN];
void dfs(int u,int fa)
{
siz[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
// if(v==fa)continue ;
dfs(v,u);
siz[u]+=siz[v];
a[u].push_back(siz[v]);
}
sort(a[u].begin(),a[u].end());//从小到大排序可以正着统计答案
ll s=0;
for(int i=0;i<a[u].size();i++)
{
ans+=s+1; //由于包括这个节点编号,所以要加1
s+=a[u][i];
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf(" %s",s);
int u=0,len=strlen(s);
for(int j=len-1;j>=0;j--)
{
int c=s[j]-'a';
if(!ch[u][c])
{
ch[u][c]=++idx;
fa[idx]=u;
}
u=ch[u][c];
}
val[i]=u;
ed[u]=i;
}
for(int i=1;i<=n;i++)
{
int k=fa[val[i]];
while(k&&!ed[k])k=fa[k];
add_edge(ed[k],i);
}
dfs(0,0);
printf("%lld\n",ans);
return 0;
}