zoukankan      html  css  js  c++  java
  • 「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
    ",ans);
        for(int i=1;i<=n;i++) cout<<name[i]<<endl;
        return 0;
    }
  • 相关阅读:
    解决PLSQL Developer中文横着显示的问题
    品优购_day06
    品优购_day05
    品优购_day04
    品优购_day03
    品优购_day02
    java 学习中遇到的问题(二)泛型中<? extends T>和<? super T>的区别
    java 学习中遇到的问题(一)方法调用中的值传递和引用传递
    java中的字符串比较
    自学java 第十一章持有对象
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/10506239.html
Copyright © 2011-2022 走看看