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;
}
posted @ 2021-02-11 20:47  jz_597  阅读(76)  评论(0编辑  收藏  举报