zoukankan      html  css  js  c++  java
  • IOI2021集训队作业189KD Dictionary

    (n)个字符串,你需要构造一棵字典树,使得这些字符串都是字典树的子串(即存在祖先后代链构成这个字符串)。

    最小化字典树的点数。

    (nle 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("
    "))
    //		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
    ",sum+tmp);
    //	for (int i=1;i<=n;++i)
    //		printf("%d ",ef[i]);
    //	printf("
    ");
    //	return 0;
    	int tmp=work();
    	build();
    	printf("%d
    ",tot);
    //	return 0;
    	printf("0
    ");
    	for (int i=2;i<=tot;++i)
    		printf("%d %c
    ",fa[i],c[i]);
    	return 0;
    }
    
  • 相关阅读:
    程序员创业第二步:五个角度打造企业世界级竞争力
    开源题材征集 + MVC&EF Core 完整教程小结
    MVC+EF Core 完整教程20--tag helper详解
    MVC5+EF6 入门完整教程13 -- 动态生成多级菜单
    加载驱动三种的方法
    Caused by: javax.el.PropertyNotFoundException: Property [userName] not found on type [java.lang.String]
    Eclipse 中的 insert spaces for tabs 设置方法
    Windows中mysql的配置文件,解决字符集编码问题,统一使用utf8字符集
    ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated ......问题报错解决办法!
    Java中的类三种类加载器+双气委派模型
  • 原文地址:https://www.cnblogs.com/jz-597/p/14398155.html
Copyright © 2011-2022 走看看