题目大意
给你n个长度不超过10的小写字母字符串。
请你构造一颗节点数最少的字典树。
使得对于任意一个人给定字符串,字典树中都存在一条祖先后代链对应的字符串与其相等。
做法
首先显然,如果字符串a包含字符串b,可以直接剔除字符串b。
我们考虑最优解,一定是按照某个顺序添加字符串进入字典树中。
假设现在有字符串c,可以找到其一个最长前缀,使得该前缀可以被字典树表示,然后把剩余后缀加入。
如果c的最长前缀由原本多个字符串的子串构成,第一个是a,第二个是b。
不妨将c的加入顺序调到b之前,解不会变劣。
如此可以发现,最优顺序每一个字符串一定依赖于原本的某个字符串,即它的某个前缀是它的子串。
建立0号点,向第i个点连一条长度为|s[i]|的有向边。
第i个点向第j个点连长度为k的有向边,k是一个最大的整数使得s[j]的前缀k是s[i]的子串。
那么做一个以0为根的最小树形图即可求出答案。
本题要求输出方案,需要逐层展开树形图。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=50+10,inf=100000000;
char s[maxn][maxn];
int belong[maxn][maxn],len[maxn],cnt[maxn],pre[maxn][maxn],dis[maxn][maxn],zz[maxn][maxn];
int data[maxn][maxn][maxn],from[maxn][maxn][maxn],go[maxn][maxn][maxn],In[maxn][maxn],a[maxn][maxn];
bool bz[maxn],pd[maxn],dp[maxn][maxn];
int vis[maxn];
int h[maxn],qx[maxn],nxt[maxn];
int g[500+10][30],fa[500+10],val[500+10],hh[maxn];
int i,j,k,l,t,n,m,tot,top,ans,root;
int getk(int x,int y){
int i,k,t=0;
fo(i,1,len[y]){
k=0;
while (i+k<=len[y]&&1+k<=len[x]&&s[x][1+k]==s[y][i+k]) k++;
if (k>t){
t=k;
zz[y][x]=i+k-1;
}
}
return t;
}
void update(int id,int x,int y,int s,int t,int v){
if (v<data[id][x][y]){
data[id][x][y]=v;
from[id][x][y]=s;
go[id][x][y]=t;
}
}
void work(int id){
int i,j,k,l=0,r,t;
int n=cnt[id];
bool czy=1;
fo(i,1,n){
In[id][i]=data[id][0][i];
a[id][i]=0;
fo(j,1,n){
if (data[id][j][i]<In[id][i]){
In[id][i]=data[id][j][i];
a[id][i]=j;
}
}
ans+=In[id][i];
}
fo(i,1,n) bz[i]=dp[id][i]=vis[i]=0;
fo(i,1,n)
if (!bz[i]){
k=i;
while (k&&!bz[k]){
bz[k]=1;
vis[k]=i;
k=a[id][k];
}
if (k&&vis[k]==i){
czy=0;
j=k;
++l;
while (1){
dp[id][k]=1;
belong[id][k]=l;
if (a[id][k]==j) break;
k=a[id][k];
}
}
}
if (czy){
fo(i,1,n) pre[id][i]=a[id][i];
return;
}
fo(i,1,n)
if (!belong[id][i]) belong[id][i]=++l;
fo(i,0,n)
fo(j,0,n)
data[id+1][i][j]=inf;
cnt[id+1]=l;
fo(i,0,n)
fo(j,0,n)
if (belong[id][i]!=belong[id][j]){
r=belong[id][i];t=belong[id][j];
/*if (!dp[id][i]&&dp[id][j]) update(id+1,r,t,i,j,data[id][i][j]-In[id][j]);
else update(id+1,r,t,i,j,data[id][i][j]);*/
update(id+1,r,t,i,j,data[id][i][j]-In[id][j]);
}
work(id+1);
fo(i,1,n)
if (!dp[id][i]){
/*pre[id][i]=a[id][i];*/
j=belong[id][i];
pre[id][i]=from[id+1][pre[id+1][j]][j];
}
else{
j=belong[id][i];
k=go[id+1][pre[id+1][j]][j];
if (k==i) pre[id][i]=from[id+1][pre[id+1][j]][j];
else pre[id][i]=a[id][i];
}
}
void add(int x,int y){
qx[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
void dfs(int x){
if (x){
if (pre[0][x]){
int j=hh[pre[0][x]];
int t=len[pre[0][x]]-zz[pre[0][x]][x];
while (t--) j=fa[j];
int i=len[x]-dis[pre[0][x]][x]+1;
while (i<=len[x]){
top++;
fa[top]=j;
val[top]=s[x][i]-'a';
j=top;
i++;
}
hh[x]=j;
}
else{
int i=1,j=root;
while (i<=len[x]){
top++;
fa[top]=j;
val[top]=s[x][i]-'a';
j=top;
i++;
}
hh[x]=j;
}
}
int t=h[x];
while (t){
dfs(qx[t]);
t=nxt[t];
}
}
int main(){
freopen("dictionary.in","r",stdin);
//freopen("32","r",stdin);
freopen("dictionary.out","w",stdout);
scanf("%d",&n);
fo(i,1,n){
scanf("%s",s[i]+1);
len[i]=strlen(s[i]+1);
}
fo(i,1,n) pd[i]=1;
fo(i,1,n)
fo(j,1,n)
if (i!=j){
dis[i][j]=len[j]-getk(j,i);
if (!dis[i][j]) pd[j]=0;
}
top=0;
fo(i,1,n){
if (pd[i]){
top++;
fo(k,1,len[i]) s[top][k]=s[i][k];
len[top]=len[i];
}
}
fo(i,0,n)
fo(j,0,n)
dis[i][j]=inf;
n=top;
fo(i,1,n)
fo(j,1,n)
if (i!=j){
dis[i][j]=len[j]-getk(j,i);
if (dis[i][j]==len[j]) dis[i][j]=inf;
}
fo(i,1,n) dis[0][i]=len[i];
cnt[0]=n;
fo(i,0,n)
fo(j,0,n)
data[0][i][j]=dis[i][j];
work(0);
fo(i,1,n) add(pre[0][i],i);
top=root=hh[0]=1;
dfs(0);
printf("%d\n",top);
//printf("%d\n",ans);
fo(i,1,top)
if (!fa[i]) printf("0\n");
else printf("%d %c\n",fa[i],val[i]+'a');
}