zoukankan      html  css  js  c++  java
  • uva 753 A Plug for UNIX

    最大流 OR 二分图匹配

    题意:输入n,有n个插座,下面n行是每个插座的类型(最后24个字母来表示一个插座,没有空格放心用scanf,但是有可能插座会相同,但是这个没有什么影响)

             输入m,有m个电器,下面m行每行两个单词分别是电器的名字和插头类型(同样24个字母单词内没空格,两个单词空格隔开)

             输入k,有k个转换器,下面k行每行两个单词,分别表示转换器的入口类型和插头类型

    每种转换器的个数是无限的,转换器本身可以与转换器相连

    要你求,让最多的电器能够插在插座上(可以用转换器辅助也可以直接插上去),输入不能插上去的电器的数量

    思路一:转化为最大流,重点还是如何建图,我是用邻接表建图的这样效率也高一点,其实用邻接矩阵建图也可以。我的建图方法是,电器从1到m标号,转换器从m+1到m+k标号,插座从m+k+1到m+k+n标号,另外设置一个源点s=0,汇点t=m+k+n+1 , 求s到t的最大流

    s与所有电器建一条有向边,容量为1,所有插座与汇点建一条有向边,容量为1。对于每个电器,如果能直接和插座相连的,和每个能相连的插座建一条有向边,容量为1.另外,所有电器和所有能连接上的转换器建一条有向边,容量为INF,转换器和转换器之间能相连的建一条有向边容量为INF,转换器和插座能相连建一条有向边容量为INF。

    建图完毕,直接EK最大流模板上去即可

    注意几点:

    1.邻接表建有向边不要忘记每条有向边附带一条反边,反边的容量为0

    2.一定要记得建立电器和插座直接相连的边

    3.一开始想复杂了,认为用电器和插座要拆点,转换器不用拆点,其实全部都不用拆点

    4.不需要建无向图,另外建无向图应该是错的

    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define MAXN 550
    #define MAXM 50010
    int N,M,K,CNT,edgenum,s,t,first[MAXN];
    struct edge
    {
        int u,v,f,cap,next;
    }e[MAXM];
    struct  device  //电器
    {
        char s1[30],s2[30];
    }d[MAXN];
    struct adapter //转换器
    {
        char s1[30], s2[30];
    }a[MAXN];
    struct receptacle  //插座
    {
        char s[30];
    }r[MAXN]; 
    
    
    void printfff()  //测试打印函数
    {
        printf("M=%d  N=%d   K=%d  CNT=%d  s=%d   t=%d\n",M,N,K,CNT,s,t);
        for(int i=0; i<edgenum; i++)
            printf("%d:  %d-->%d=%d %d\n",i,e[i].u,e[i].v,e[i].cap,e[i].next);
    }
    void add(int u  ,int v , int cap)
    {
        int t;
        e[edgenum].u=u; e[edgenum].v=v;
        e[edgenum].f=0; e[edgenum].cap=cap;
        e[edgenum].next=first[u];
        first[u]=edgenum++;
    
        t=u; u=v; v=t;
    
        e[edgenum].u=u; e[edgenum].v=v;
        e[edgenum].f=0; e[edgenum].cap=0;
        e[edgenum].next=first[u];
        first[u]=edgenum++;
    }
    void input()
    {
        scanf("%d",&N);
        for(int i=1; i<=N; i++)
            scanf("%s",r[i].s);
    
        scanf("%d",&M);
        for(int i=1; i<=M; i++)
            scanf("%s%s",d[i].s1,d[i].s2);
    
        scanf("%d",&K);
        for(int i=1; i<=K; i++)
            scanf("%s%s",a[i].s1,a[i].s2); 
    
        CNT=N+M+K;
    }
    void init()
    {
        memset(first,-1,sizeof(first));
        edgenum=0;
        s=0;  t=CNT+1; //源点,汇点
        for(int i=1; i<=M; i++)  //所有电器
        {
            add(s,i,1);  //源点和电器建边容量为1
            
            for(int j=1; j<=K ; j++) //所有电器和转换器的连接
                if(!strcmp(d[i].s2 , a[j].s1)) //电器可以插入转换器中
                    add(i,j+M,INF);
    
            for(int j=1; j<=N; j++)  //电器和插座直接相连
                if(!strcmp(d[i].s2 , r[j].s))
                    add(i,M+K+j,1);
        }
    
        for(int i=1; i<=K; i++) 
        {
            for(int j=1; j<=K; j++) //所有转换器和转换器之间的连接
                if(i!=j && !strcmp(a[i].s2 , a[j].s1))  
                    //不用考虑相同种类的转换器的连接,因为没有意义
                    add(i+M,j+M,INF);
    
            for(int j=1; j<=N; j++) //所有转换器和所有插座连接
                if(!strcmp(a[i].s2 , r[j].s)) //转换器可插入插座中
                    add(i+M , M+K+j , INF);  //转化器和插座相连
        }
    
        for(int i=1; i<=N; i++)  //插座和汇点相连
            add(M+K+i,t,1);
    }
    void EK()
    {
        queue<int>q;
        int a[MAXN],path[MAXN];
        int F;
        F=0;
        while(1)
        {
            memset(path,-1,sizeof(path));
            memset(a,0,sizeof(a));
            a[s]=INF;
            q.push(s);
            while(!q.empty())
            {
                int u=q.front(); q.pop();
                for(int k=first[u]; k!=-1; k=e[k].next)
                {
                    int v=e[k].v;
                    if(!a[v] && e[k].f<e[k].cap)
                    {
                        a[v]=a[u]<e[k].cap-e[k].f?a[u]:e[k].cap-e[k].f;
                        path[v]=k;
                        q.push(v);
                    }
                }
            }
    
            if(!a[t]) break;
    
            for(int k=path[t]; k!=-1; k=path[e[k].u]) //增广
            {
                //printf("%d<--%d ",e[k].v,e[k].u);
                e[k].f+=a[t];
                e[k^1].f-=a[t];
            }
            //printf("\n");
            F+=a[t];
        }
    
        //printf("F=%d\n",F);
        printf("%d\n",M-F);
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            input();
            init();
            //printfff();
            EK();
            if(T) printf("\n");
        }
        return 0;
    }

    思路二:二分图最大匹配

    其实用二分图的思想来看这个问题更加直观,即电器和插座匹配,(电器之间,插座之间不可能匹配)。问题同样是建图问题

    用最大流建图的话,顶点会有转换器,但是二分图来建则没有(当然也不需要加额外的源点和汇点)。二分图建立的分两部分,一部分是,用电器能不能直接和某些插座直接相连,能的话直接建边。另外要利用转换器,看看能不能把不能直接相连电器和插座给连接起来,能的话就连接。我还没写,但是想到是用dfs去找,不知道效率如何。

    最后建了二分图,不需要管那些边是怎么连接的(直接相连或者借助了转换器),直接把匈牙利丢上去即可

    明天起来再写二分图了

    二分图写好了,思路就是上面说的那样,嗯写二分图舒心多了,建边少代码也少一些。不过我是用dfs去判断电器能不能通过转换器连到插座上的,所以时间慢了点,0.060s,上面的最大流是0.008s

    另外一个地方是。电器不能直接与插座相连,然后用dfs去找能不能通过转换器去相连,虽然每种转换器的数量是无限的,但是在一趟寻找中一种转换器没必要被用两次(其实那种入口和插头相同的转换器也没必要用上,但是不做特殊判断了,没影响),也就是可以看成路径,虽然有环,但是这个环显然是没必要经过,所以加了一个vis数组标记

    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define MAXN 250
    #define MAXM 40100
    int N,M,K,CNT,edgenum,first[MAXN],mat[MAXN],vis[MAXN],cov[MAXN];
    struct edge
    {
        int u,v,next;
    }e[MAXM];
    struct  device  //电器
    {
        char s1[30],s2[30];
    }d[MAXN];
    struct adapter //转换器
    {
        char s1[30], s2[30];
    }a[MAXN];
    struct receptacle  //插座
    {
        char s[30];
    }r[MAXN]; 
    void add(int u , int v)
    {
        e[edgenum].u=u; e[edgenum].v=v;
        e[edgenum].next=first[u];
        first[u]=edgenum++;
    }
    int dfs(int j , int k)  //目标是j插座,当前是k转换器
    {
        if(!strcmp(a[k].s2 , r[j].s))  //当前转换器能插入插座中
            return 1;
        vis[k]=1;  //标记k转换器被用过,在一趟dfs中一种转换器没必要被用两次
        for(int i=1; i<=K; i++) if(!vis[i]) //所有没被用过的转换器
            if(!strcmp(a[k].s2 , a[i].s1))         
            {//另外k转换器可以插入i转换器中
                if(dfs(j,i))
                    return 1;
                vis[i]=0;
            }
        return 0;
    
    }
    int find(int i , int j)  //i电器和j插座
    {
        memset(vis,0,sizeof(vis));
        for(int k=1; k<=K; k++) //所有转换器
            if(!strcmp(d[i].s2 , a[k].s1)) 
            {//电器能插入转换器的入口
                if(dfs(j,k))
                    return 1;
                vis[k]=0;
            }
        return 0;
    }
    void input()
    {
        scanf("%d",&N);
        for(int i=1; i<=N; i++)
            scanf("%s",r[i].s);
    
        scanf("%d",&M);
        for(int i=1; i<=M; i++)
            scanf("%s%s",d[i].s1,d[i].s2);
    
        scanf("%d",&K);
        for(int i=1; i<=K; i++)
            scanf("%s%s",a[i].s1,a[i].s2); 
    
        CNT=N+M+K;
    }
    void init()
    {
        memset(first,-1,sizeof(first));
        edgenum=0;
        for(int i=1; i<=M; i++)  //所有电器 
            for(int j=1; j<=N; j++) //所有插座
            {
                if(!strcmp(d[i].s2 , r[j].s))  //可以直接相连
                {
                    //printf("直接相连%d---%d\n",i,j);
                    add(i,j+M);  //建立有向边
                }
                else if(find(i,j))  //如果通过转换器能连接上
                {
                    //printf("通过转换器连接%d---%d\n",i,j);
                    add(i,j+M);
                }
            }
    }
    void printfff()
    {
        for(int i=0; i<edgenum; i++)
            printf("%d %d %d\n",e[i].u,e[i].v,e[i].next);
    }
    int dfs_match(int u)
    {
        for(int k=first[u]; k!=-1; k=e[k].next)
        {
            int v=e[k].v;
            if(!cov[v])
            {
                cov[v]=1;
                if( mat[v]==-1 || dfs_match(mat[v]) )
                { mat[v]=u; return 1; }
            }
        }
        return 0;
    }
    void maxmatch()
    {
        int max=0;
        memset(mat,-1,sizeof(mat));
        for(int i=1; i<=M+N; i++)  //所有的点都做一次起点
        {
            memset(cov,0,sizeof(cov));
            max+=dfs_match(i);
        }
        //for(int i=1; i<=M+N; i++)
            //printf("%d<--->%d\n",i,mat[i]);
        //printf("max=%d\n",max);
        printf("%d\n",M-max);
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            input();
            init();
           //printfff();
            maxmatch();
            if(T) printf("\n");
        }
        return 0;
    }
  • 相关阅读:
    UnityVS(Visual Studio Tools For Unity)的安装与使用
    Balsamiq Mockups注册码
    python基础之os.system函数
    jenkins配置记录(1)--添加用户权限
    chromedriver与chrome各版本及下载地址
    高阶面试官应掌握哪些面试技巧
    [面试技巧]16个经典面试问题回答思路
    自动代码质量分析(GitLab+JenKins+SonarQube)
    Git提交代码自动触发JenKins构建项目
    Allure 安装及使用
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2828671.html
Copyright © 2011-2022 走看看