IOI2021集训队作业189KD Dictionary
给\(n\)个字符串,你需要构造一棵字典树,使得这些字符串都是字典树的子串(即存在祖先后代链构成这个字符串)。
最小化字典树的点数。
\(n\le 50,|s_i|\le 10\)
考虑字符串之间共用字母的关系。猜想:加入\(s_{n+1}\)表示空串。在最优的方案中,对于每个非空字符串\(s\),一定能找到一个\(t\),让\(s\)的一段前缀和\(t\)的一个子串重合,除此之外\(s\)与其它字符串没有共用的字母。下文称\(s\)依赖于\(t\)。依赖关系呈树形结构。
证明考虑如果某字符串和另外多个字符串有不同的共用部分,一处为前缀,其它地方为子串。此时可以调整关系,使得它们依旧满足依赖关系。比如\(B\)依赖于\(A\),\(C\)依赖于\(A\)但同时又和\(B\)有不同于\(A\)的共用部分;这时候可以把依赖关系调整为\(C\)依赖\(A\),\(B\)依赖\(C\)。
有了上面的结论,预处理两两依赖能够共用的字母数量,设\(f_{s,t}\)。问题变成构造一棵树,最大化\(\sum f_{s,fa_s}\)。
然后就是个最小树形图板子。
代码显然可以优化但是不想刚下去。
终于写了个完整的普通树形图板子……
using namespace std;
#include <bits/stdc++.h>
#define N 55
#define L 20
#define mp(x,y) make_pair(x,y)
#define fi first
#define se second
int n;
char s[N][L];
int len[N];
pair<int,int> f[N][N];
struct edge{int u,v,w;} ed[N*N],ori[N*N];
int m;
pair<int,int> calc(char s[],int n,char t[],int m){
int mx=-1,pos=0;
for (int j=1;j<=m;++j){
int i=0;
for (;i<n && s[1+i]==t[j+i];++i);
if (i>mx)
mx=i,pos=j;
}
return mp(mx,pos);
}
int ef[N];
const int INF=1000000000;
int mn[N],id[N],cnt,vis[N],pre[N],pe[N];//pe[i]表示连向i的最小边的编号
int dfs(int n,int m,int r){
int res=0;
for (int i=1;i<=n;++i)
mn[i]=INF;
for (int i=1;i<=m;++i){
int u=ed[i].u,v=ed[i].v,w=ed[i].w;
if (u!=v && w<mn[v])
mn[v]=w,pre[v]=u,pe[v]=i;
}
// for (int i=1;i<=n;++i)
// if (i!=r && mn[i]==INF)
// return INF;
memset(vis,0,sizeof(int)*(n+1));
memset(id,0,sizeof(int)*(n+1));
cnt=0;
for (int i=1;i<=n;++i){
if (i==r) continue;
res+=mn[i];
int x=i;
for (;vis[x]!=i && !id[x] && x!=r;x=pre[x])
vis[x]=i;
if (x!=r && !id[x]){
id[x]=++cnt;
for (int y=pre[x];y!=x;y=pre[y])
id[y]=cnt;
}
}
if (cnt==0)
return res;
for (int i=1;i<=n;++i)
if (!id[i])
id[i]=++cnt;
int bk_pe[N];//bk表示备份
edge bk_ed[N*N];
memcpy(bk_pe,pe,sizeof(int)*(n+1));
memcpy(bk_ed,ed,sizeof(edge)*(m+1));
for (int i=1;i<=m;++i){
int u=ed[i].u,v=ed[i].v,w=ed[i].w;
ed[i]={id[u],id[v],w-(id[u]!=id[v]?mn[v]:0)};
}
int nn=cnt,nr=id[r];
res+=dfs(nn,m,nr);
static int tpe[N];
memset(tpe,0,sizeof(int)*(n+1));
for (int i=1;i<=nn;++i){
if (i==nr) continue;
tpe[bk_ed[pe[i]].v]=pe[i];//标记子问题的最小树形图的边
}
for (int i=1;i<=n;++i){
if (i==r || tpe[bk_ed[bk_pe[i]].v]) continue;
tpe[bk_ed[bk_pe[i]].v]=bk_pe[i];
}
memcpy(pe,tpe,sizeof(int)*(n+1));
return res;
}
int work(){
memcpy(ori,ed,sizeof(edge)*(m+1));
int res=dfs(n+1,m,n+1);
for (int i=1;i<=n;++i)
ef[i]=ori[pe[i]].u;
return res;
}
int tot;
int dot[N][L],fa[N*L];
char c[N*L];
void build(){
static int deg[N];
for (int i=1;i<=n;++i)
deg[ef[i]]++;
static int q[N];
int head=1,tail=0;
for (int i=1;i<=n+1;++i)
if (deg[i]==0)
q[++tail]=i;
while (head<=tail){
int x=q[head++];
if (--deg[ef[x]]==0)
q[++tail]=ef[x];
}
dot[n+1][0]=++tot;
for (int i=n;i>=1;--i){
int x=q[i],y=ef[x],l=f[x][y].fi,b=f[x][y].se;
for (int j=0;j<=l;++j)
dot[x][j]=dot[y][b+j-1];
for (int j=l+1;j<=len[x];++j){
dot[x][j]=++tot;
fa[tot]=dot[x][j-1];
c[tot]=s[x][j];
}
}
}
int main(){
freopen("dictionary.in","r",stdin);
freopen("dictionary.out","w",stdout);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i){
scanf("%s",s[i]+1);
len[i]=strlen(s[i]+1);
}
for (int i=1;i<=n;++i){
f[i][n+1]={0,1};
ed[++m]={n+1,i,0};
for (int j=1;j<=n;++j)
if (i!=j){
f[i][j]=calc(s[i],len[i],s[j],len[j]);
ed[++m]={j,i,-f[i][j].fi};
}
}
// for (int i=1;i<=n;++i,printf("\n"))
// for (int j=1;j<=n;++j)
// printf("%d ",f[i][j].fi);
// int tmp=work(),sum=1;
// for (int i=1;i<=n;++i)
// sum+=len[i];
// printf("%d\n",sum+tmp);
// for (int i=1;i<=n;++i)
// printf("%d ",ef[i]);
// printf("\n");
// return 0;
int tmp=work();
build();
printf("%d\n",tot);
// return 0;
printf("0\n");
for (int i=2;i<=tot;++i)
printf("%d %c\n",fa[i],c[i]);
return 0;
}