「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;
}

 

posted @ 2019-03-10 21:57  LLTYYC  阅读(297)  评论(0编辑  收藏  举报