「2019冬令营提高组」不同的缩写
问最长简称最短,考虑二分答案
二分后开始考虑暴力枚举合法缩写
但是正常枚举时可能会重复
所以设 $ch[i][j][k]$ 表示第 $i$ 个人,当前到位置 $j$ 时,下一个字符为 $k+'a'$ 的最前面的位置
这样我们暴力 $dfs$ 时就不会重复枚举缩写了
预处理一波 $ch$ :
for(int i=1;i<=n;i++) { scanf("%s",s+1); int len=strlen(s+1); for(int j=len-1;j>=0;j--)//注意j要处理到0 { for(int k=0;k<26;k++) if(ch[i][j+1][k]) ch[i][j][k]=ch[i][j+1][k]; ch[i][j][ s[j+1]-'a' ]=j+1; } }
设我们二分的答案为 $Len$ ,那么直接 $dfs$ 枚举所有长度小于 $Len$ 的缩写
并且可以发现,如果合法缩写数量一旦大于等于 $n$ ,那么无论如何这个名字都一定能找到合法缩写
所以一旦枚举到 $n$ 个缩写就可以直接退出 $dfs$ ,并且等等匹配时也不用考虑这个名字
然后考虑用网络流匹配剩下的名字(此时每个名字只有不到 $n$ 个缩写)
建 $n$ 个点表示 $n$ 个名字,从源点 $S$ 向剩下的名字连一条流量为 $1$ 的边,表示每个名字只能有一个缩写
枚举出每个人的缩写并把所有缩写都暴力插入到 $trie$ 里,那么 $trie$ 上的每个节点都表示一种缩写
然后把每个名字的所有缩写也建点,从这个名字向它的所有缩写在 $trie$ 上的点连一条流量为 $1$ 的边
然后 $trie$ 上的每个节点都向 $T$ 连一条流量为 $1$ 的边,表示每个缩写只能给一个名字
然后最大流,如果最大流等于剩下的名字数量则合法
设 $Mark[u]$ 维护 $trie$ 上的节点 $u$ 是作为哪个名字的缩写
那么先枚举所有网络流中的名字 $i$,暴力枚举出边,如果流量为 0 则把出边连向的点 $u$ 的 $Mark[u]$ 赋值为 $i$
其他不在网络流中的名字再来一波 $dfs$ 枚举缩写,如果一个节点 $u$ 的 $Mark[u]==0$ 则赋值为此名字的编号并直接退出
最后开一个字符栈对 $trie$ 再来一波 $dfs$ 并维护 $name[i]$ 表示名字 $i$ 的缩写
最后就是艰难的代码了
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<queue> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int M=307,N=5e5+7,INF=1e9+7; int n,ans; bool pd[M];//pd用来判断一个名字是否有超过n个缩写 int Mark[N];//Mark维护节点u被哪个名字当作缩写 namespace Dinic {//网络流模板 int fir[N],from[N],to[N],val[N],cntt=1; int S,T,dep[N],Fir[N]; queue <int> q; inline void clr() { memset(fir,0,sizeof(fir)); cntt=1; }//初始化 inline void add(int a,int b,int c) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; val[cntt]=c; from[++cntt]=fir[b]; fir[b]=cntt; to[cntt]=a; val[cntt]=0; } bool BFS() { memset(dep,0,sizeof(dep)); memcpy(Fir,fir,sizeof(fir)); q.push(S); dep[S]=1; while(!q.empty()) { int x=q.front(); q.pop(); for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(dep[v]||!val[i]) continue; dep[v]=dep[x]+1; q.push(v); } } return dep[T]>0; } int DFS(int x,int mxf) { if(x==T||!mxf) return mxf; int res=0,fl=0; for(int i=Fir[x];i;i=from[i]) { Fir[x]=i; int &v=to[i]; if(dep[v]!=dep[x]+1||!val[i]) continue; if( res=DFS(v,min(mxf,val[i])) ) { mxf-=res; fl+=res; val[i]-=res; val[i^1]+=res; if(!mxf) break; } } return fl; } int dinic() { int res=0; while(BFS()) res+=DFS(S,INF); return res; } void Mark_up()//对网络流中的名字缩写打上标记 { for(int i=1;i<=n;i++) { if(pd[i]) continue; for(int j=fir[i];j;j=from[j]) if(!val[j]) { Mark[to[j]]=i; break; } } } } int ch[M][M][27]; int cnt,Len; //char ss[N],Top;维护中间过程 void dfs1(int dep,int i,int j)//深度,名字编号,当前枚举到的位置 { if(dep) cnt++; //cout<<i<<endl; puts(ss+1); if(dep==Len||cnt>=n) return; for(int k=0;k<26;k++) { if(ch[i][j][k]) { /*ss[++Top]=k+'a';*/ dfs1(dep+1,i,ch[i][j][k]); /*ss[Top--]=0;*/ } if(cnt>=n) return; } } int Trie[N][27],rt,tot;//trie inline void clr(int p) { memset(Trie[p],0,sizeof(Trie[p])); Mark[p]=0; }//清空trie void dfs2(int dep,int u,int i,int j)//深度,当前在trie上的节点,名字编号,位置 { if(dep) Dinic::add(i,u,1);//连边 if(dep==Len) return; for(int k=0;k<26;k++) { if(!ch[i][j][k]) continue; if(!Trie[u][k]) Trie[u][k]=++tot,clr(tot);//动态清空trie新节点 dfs2(dep+1,Trie[u][k],i,ch[i][j][k]); } } bool check()//判断二分的Len是否满足条件 { Dinic::clr();//记得清空 rt=tot=n+1; clr(tot);//前面有n个节点作为名字 int mxfl=0; memset(pd,0,sizeof(pd));//记得清空 for(int i=1;i<=n;i++) { cnt=0; dfs1(0,i,0); if(cnt>=n) { pd[i]=1; continue; }//如果缩写>=n就不用考虑 mxfl++; dfs2(0,rt,i,0); } Dinic::S=tot+1,Dinic::T=tot+2; for(int i=1;i<=n;i++) if(!pd[i]) Dinic::add(Dinic::S,i,1); for(int i=rt+1;i<=tot;i++) Dinic::add(i,Dinic::T,1);//连边 return Dinic::dinic()==mxfl; } bool dfs3(int dep,int u,int i,int j)//枚举不在网络流的点的缩写 { if(dep&&!Mark[u]) { Mark[u]=i; return 1; }//随便找一个打上标记 if(dep==ans) return 0; for(int k=0;k<26;k++) { if(!ch[i][j][k]) continue; if(!Trie[u][k]) Trie[u][k]=++tot,clr(tot);//注意可能之前trie上没有此节点 if( dfs3(dep+1,Trie[u][k],i,ch[i][j][k]) ) return 1; } return 0; } string name[M],st; void dfs4(int u)//处理name { if(Mark[u]) name[Mark[u]]=st; for(int k=0;k<26;k++) { if(!Trie[u][k]) continue; st+=(k+'a'); dfs4(Trie[u][k]); st.erase(st.size()-1); } } char s[M]; int main() { freopen("diff.in","r",stdin); freopen("diff.out","w",stdout); n=read(); for(int i=1;i<=n;i++) { scanf("%s",s+1); int len=strlen(s+1); for(int j=len-1;j>=0;j--) { for(int k=0;k<26;k++) if(ch[i][j+1][k]) ch[i][j][k]=ch[i][j+1][k]; ch[i][j][ s[j+1]-'a' ]=j+1; } }//处理ch int l=1,r=M; while(l<=r) { Len=l+r>>1; if(check()) r=Len-1,ans=Len; else l=Len+1; } Len=ans; if(!check()) { printf("-1"); return 0; }//注意最后一定要先check一下ans //这样之后的网络流才是我们要的网络流 Dinic::Mark_up(); for(int i=1;i<=n;i++) if(pd[i]) dfs3(0,rt,i,0); st=""; dfs4(rt); printf("%d\n",ans); for(int i=1;i<=n;i++) cout<<name[i]<<endl; return 0; }