题目大意:有n个插座,m个设备,k个适配器,每种都小于等于100。现在给出插座的种类和个数,给出设备的种类和个数,再给出已有的适配器种类,每种无限个,问最少会剩下几个设备没有插座充电.。
题目来源:刘汝佳紫书上的网络流例题。
方法思路:这个例题让我学习到了网络流的标准建模思路,附带巩固了Floyd的传递闭包的思路,就是计算从一点到另一点是否有边。说实话网络流建模是真的难,这里我们先选定一个超级源点和超级汇点,将所有的插头与源点相连,容量为1,然后所有插座与汇点相连,容量也为1;然后就是最关键的一部:插座与插头之间的建边。因为适配器的数量是无限的,所以我们应该计算出任意一种插头是否可以通过适配器变为另一种,这是,我们把插头和插座的类型当做点,适配器当作边,然后用之前说的floyd扫描一遍整个图,就可以计算出任意一个点是否可以到达任意另一个点,如果可以,就建边,容量为无穷大(表示可以有任意多的设备通过适配器转换).
最后,调用EK算法计算最大流,答案就是插头的总数-最大流数。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <map>
using namespace std;
/*
网络流模型就是将所有的插头与源点相连,
所有的插座与汇点相连,而将所有转换器建立为
插头与插座的边
floyd传递闭包+最大流
*/
const int maxn=410;
const int maxm=maxn*maxn;
const int inf=0x3f3f3f3f;
typedef long long ll;
const int src=0;//超级源点
const int tar=maxn-1;//超级汇点
vector<int>device;//插头
vector<int>target;//插座
int f[maxn][maxn];
string ss,s;
map<string,int>mp;//用数组表示插座,插头种类
struct Edge
{
int from;
int to;
int cap;
int flow;
Edge(int u,int v,int cap,int flow):from(u),to(v),cap(cap),flow(flow){}
};
vector<Edge>edges;
vector<int>G[maxn];
int p[maxn];//最短路上弧的变量
int a[maxn];//可改进量,残量
void init(int n)
{
for(int i=0;i<=n;i++)G[i].clear();
edges.clear();
}
void add_edge(int u,int v,int cap)
{
edges.push_back(Edge(u,v,cap,0));
edges.push_back(Edge(v,u,0,0));
int m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
int maxflow(int s,int t)
{
int flow=0;
for(;;)
{
memset(a,0,sizeof(a));
queue<int>Q;
Q.push(s);
a[s]=inf;
while(!Q.empty())
{
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++)
{
Edge &e=edges[G[x][i]];
if(!a[e.to]&&e.cap>e.flow)
{
p[e.to]=G[x][i];
a[e.to]=min(a[x],e.cap-e.flow);
Q.push(e.to);
}
}
if(a[t])break;
}
if(!a[t])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;
}
void Floyd(int n)
{
//Floyd传递闭包,求边的连通
for(int k = 0; k < n; k++){
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
f[i][j] =f[i][j]||(f[i][k]&&f[k][j]);//计算任意节点i能否通过任意边到达下一个节点j
}
}
}
}
int main()
{
int T;
int n,m,k;
scanf("%d",&T);
printf("\n");
while(T--)
{
mp.clear();
device.clear();
target.clear();
scanf("%d",&n);
//cout<<n<<endl;
int tot=1;
for(int i=0;i<n;i++)
{
cin>>s;
if(mp[s]==0)//没有匹配过
{
mp[s]=tot;//编号
device.push_back(mp[s]);
tot++;
}
else
{
device.push_back(mp[s]);
}
}
scanf("%d",&m);
for(int i=0;i<m;i++)
{
cin>>ss>>s;
if(mp[s]==0)
{
mp[s]=tot;
target.push_back(mp[s]);
tot++;
}
else
{
target.push_back(mp[s]);
}
}
scanf("%d",&k);
memset(f,0,sizeof(f));
for(int i=0;i<k;i++)
{
cin>>ss>>s;
if(mp[ss]==0)
{
mp[ss]=tot++;
}
if(mp[s]==0)
{
mp[s]=tot++;
}
f[mp[s]][mp[ss]]=1;//有边就标记为1
}
for(int i=1;i<tot;i++)
f[i][i]=1;
Floyd(tot);
int t=device.size()+target.size()+1;
init(t);
//超级源点到每个device建边
for(int i=0;i<device.size();i++)
{
add_edge(src,i+1,1);
}
//超级汇点到每个target建边
for(int i=0;i<target.size();i++)
{
add_edge(device.size()+i+1,tar,1);
}
//device和target之间可以通过转换器到达时建边
for(int i=0;i<device.size();i++)
{
for(int j=0;j<target.size();j++)
{
if(f[device[i]][target[j]]==0)continue;
int from,to,cap;
from=i+1;
to=device.size()+j+1;
cap=1;
add_edge(from,to,cap);
}
}
printf("%d\n",target.size()-maxflow(src,tar));
printf("\n");
}
return 0;
}