例题11-7 UNIX插头(A Plug for UNIX, UVa753)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/richenyunqi/article/details/89600793

欢迎访问我的Uva题解目录哦 https://blog.csdn.net/richenyunqi/article/details/81149109

题目描述

例题11-7 UNIX插头(A Plug for UNIX, UVa753)题目描述

题意解析

有n个插座,m个设备和k(n,m,k≤100)种转换器,每种转换器都有无限多。已知每个插座的类型,每个设备的插头类型,以及每种转换器的插座类型和插头类型。插头和插座类型都用不超过24个字母表示,插头只能插到类型名称相同的插座中。
例如,有4个插座,类型分别为A, B, C, D;有5个设备,插头类型分别为B, C, B, B, X;还有3种转换器,分别是B->X,X->A和X->D。这里用B->X表示插座类型为B,插头类型为X,因此一个插头类型为B的设备插上这种转换器之后就“变成”了一个插头类型为X的设备。转换器可以级联使用,例如插头类型为A的设备依次接上A->B,B->C,C->D这3个转换器之后会“变成”插头类型为D的设备。
要求插的设备尽量多。问最少剩几个不匹配的设备。

算法设计

以所有插头、插座的类型为结点,建立有向图。我们可以设置一个源点,不妨令其编号为0,然后从源点到所有插头类型连一条边,该边容量为该类型的插头的个数。然后设置一个汇点,不妨令其编号为400,从所有插座类型往汇点连一条边,边的容量为该类型的插座的个数。最后每个转换器代表一条边,边得容量为无穷大。利用最大流算法即可得出从源点到汇点的最大流,则m-最大流即为结果。

注意点

k个转换器中涉及的插头类型不一定是接线板或者设备中出现过的插头类型。在最坏情况下,100个设备,100个插座,100个转换器最多会出现400种插头。如果编码不当,这样的情况可能会让你的程序出现下标越界等运行错误。

C++代码

#include<bits/stdc++.h>
using namespace std;
struct Edge{
    int from,to,cap,flow;
    Edge(int f,int t,int c,int fl):from(f),to(t),cap(c),flow(fl){}
};
vector<Edge>edges;
vector<vector<int>>graph(405);
const int INF=0x3fffffff;//无穷大
int t,n,m,k;
int getID(const string&s,unordered_map<string,int>&trans,array<unordered_map<int,int>,3>&num,int flag){
    if(trans.find(s)==trans.end())
        trans.insert({s,trans.size()+1});
    ++num[flag][trans[s]];//递增属于该类型的插座或插头个数
    return trans[s];//返回该类型的结点编号
}
void insertEdge(int id1,int id2,int cap){//插入边
    graph[id1].push_back(edges.size());
    edges.push_back(Edge(id1,id2,cap,0));
    graph[id2].push_back(edges.size());
    edges.push_back(Edge(id2,id1,0,0));
}
int MaxFlow(int s,int t){//最大流算法,s为源点,t为汇点
    int a[405]={0},p[405]={0},flow=0;//a数组表示源点到结点a[i]的残量,p数组表示最短路树上到达结点p[i]的边在edges数组中的序号,最大流量
    while(true){//广度优先遍历查找从源点到达汇点的增广路
        memset(a,0,sizeof(a));//将源点到达每个结点的残量初始化为0
        queue<int>q;
        q.push(s);
        a[s]=INT_MAX;//起点的残量置为无穷大
        while(!q.empty()){
            int x=q.front();
            q.pop();
            for(int i:graph[x]){//遍历以x为起点的边
                Edge&e=edges[i];
                if(a[e.to]==0&&e.cap>e.flow){//当前边的终点的残量为0且容量大于流量
                    p[e.to]=i;//更新到达该终点的边的编号
                    a[e.to]=min(a[x],e.cap-e.flow);//更新源点到该终点的残量
                    q.push(e.to);//压入队列
                }
            }
            if(a[t]!=0)//终点的残量不为零,跳出循环
                break;
        }
        if(a[t]==0)//终点的残量为零,表示不存在增广路了,跳出外层死循环
            break;
        for(int u=t;u!=s;u=edges[p[u]].from){//从汇点向前遍历增广路经,更新每条增广路的流量
            edges[p[u]].flow+=a[t];
            edges[p[u]^1].flow-=a[t];
        }
        flow+=a[t];//增加最大流量
    }
    return flow;
}
int main(){
    scanf("%d",&t);
    for(int ii=0;ii<t;++ii){
        printf("%s",ii>0?"\n":"");
        edges.clear();
        fill(graph.begin(),graph.end(),vector<int>());
        scanf("%d",&n);
        string word,word2;
        unordered_map<string,int>trans;
        array<unordered_map<int,int>,3>num;//存储插座、插头、转换器中出现的每个类型的设备个数
        for(int i=0;i<n;++i){
            cin>>word;
            getID(word,trans,num,0);
        }
        scanf("%d",&m);
        for(int i=0;i<m;++i){
            cin>>word2>>word;
            getID(word,trans,num,1);
        }
        scanf("%d",&k);
        for(int i=0;i<k;++i){
            cin>>word>>word2;
            int id1=getID(word,trans,num,2),id2=getID(word2,trans,num,2);
            insertEdge(id1,id2,INF);
        }
        for(auto&i:num[1])//从源点到所有插头类型连一条边
            insertEdge(0,i.first,i.second);
        for(auto&i:num[0])//从所有插座类型往汇点连一条边
            insertEdge(i.first,400,i.second);
        printf("%d\n",m-MaxFlow(0,400));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/richenyunqi/article/details/89600793