“完全不知,往日为何,能与我……”
好吧我想说完全不知道为什么我觉得这几天对图论的学习都很有意思,学起来又酷又炫的~
刘汝佳大神的源码我会放在后面。
/*SE:wn------王宁*/
/*回过头去学二分图匹配的匈牙利算法,才发现原来这一题就是二分图匹配啊
关键的一点就是这个转换器是给带插头的设备使用的
有点,有边——int n,m;
要知道和点连接的边——void addedge()——在增加边的时候要改变m的值
然后要把点和边联系起来+定义edge结构体起点终点容量流量
要能够通过终点回溯——定义p数组
要知道这条路上的可行流量——定义a数组
要提供反悔操作——增加反向边
无论是什么数组,使用之前都要clear——queue没有clear,所以每次使用的话重新定义即可
重复的部分写成函数——再谈addedge
本题一开始的难点在于如何建立设备与插座之间的
你有n个插座,m个设备,但是是以对应的“规格”来建立关系的
所以既要保存全部信息——除了设备的名字
又要加入对应的规格信息——getnu函数应运而生
我们又知道了能够转换的关系之后——这个也要靠getnu来实现,因为你不能用字符串去作为数组下标吧?
就可以依照设备或者插座的的编号信息来建立联通的边了
边上的点就可以用对应的编号来addedge了
然而我是严格按照个体来建立边的,也就是说每一个东西我都有
给它们一个独立的编号,例如在样例中1-5是设备编号
6-9是插座编号——能联通则建一条容量为INF的边
起点到设备和插座到终点也连上容量为INF的边
之后就是求s-t最大流的过程了
:这段话是我的自我总结,你可能看不懂的。
总之就是现实例子会让你更加理解增广路但是你可能认识的不全面。
所以——多刷题
在这个过程中发现如果纯粹用现实生活的例子来解释增广路
我之前的自我解读是不全面的,之前的解读是
遇到重复边后新路流量走老路
老路上对应的等量流量去走新路
但在这里(按照我的建图方式)则是
新路的共享边(对于它的正向)之后部分给老路走
新路则走共享边(对于老路的正向)之后部分
然而刘汝佳大神则是直接按照规格来建模
这就很我佛慈悲了,他的做法也不用考虑
插座和设备规格一样的情况,因为在他的建图方式这两个东西(规格一样的)完全是
一个点
不不不,如果是同一个规格的,
假设aaa-aaa
起点是不是仍然连了三条边到a去?
a是不是仍然连接了三条边到终点去?
边依旧是存在的
边依旧是存在的啊,所以
如果是从同一个点进去又直接到终点
说明选择的是同一个规格的设备和插座
如果不是的话——走另外的点,说明是能通过转换器达到统一规格的设备和与之转换后规格相同的插座
比如有ab两种设备,通过转换b能去插a规格的插座 a能去插c规格的插座
刚好有ac两种插座
一开始我先给a设备和a插座配对(只有两条边一个中转点
后来b要去插a,a插座是不是已经被a设备插了
但是刘汝佳大神的方法就呈现出一种我可以让你去插c的感觉
而完全没有用到反悔这个招数
而实际上a设备是又去和c插座匹配
让b设备去和a插座匹配了
所以啊,多刷题,感受思维的奥妙*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=400 + 5;
const int INF = 2000000000;
int d[maxn][maxn];
int runs,run;
vector<int> G[maxn];
vector<string> v;//存放出现过的类型
int aa[maxn];
int p[maxn];
struct rec{
string name;
int nu;
};
struct pl{
string name;
int nu;
};
int getnu(string x){
for(int i=0;i<v.size();i++){
if(v[i]==x) return i;
}
v.push_back(x);
return v.size()-1;
}
struct edge{
int from,to,cap,flow;
edge(int u,int v,int w,int f):from(u),to(v),cap(w),flow(f){}
};
vector<edge> Edges;
void addEdge(int u,int v,int c){
Edges.push_back(edge(u,v,c,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 main()
{
int n,m,k,i,j,XH,hs,cnt,first=1,t,s;
string tmp,tmp2; rec a; pl b;
scanf("%d",&runs);
for(run=1;run<=runs;run++){
for(i=0;i<maxn;i++) G[i].clear();
memset(d,0,sizeof(d)); Edges.clear(); v.clear();
vector<rec> REC;
vector<pl> PL;
scanf("%d",&n);
for(i=1;i<=n;i++){
cin>>tmp;
a.name=tmp;
a.nu=getnu(tmp);
REC.push_back(a);
}
scanf("%d",&m);
for(i=1;i<=m;i++){
cin>>tmp; cin>>tmp;
b.name=tmp;
b.nu=getnu(tmp);
PL.push_back(b);
}
for(i=0;i<maxn;i++) d[i][i]=1;
scanf("%d",&k);
for(i=1;i<=k;i++){
cin>>tmp>>tmp2;
d[getnu(tmp)][getnu(tmp2)]=1;
}
XH=v.size();
for(int mi=0;mi<XH;mi++)
for(i=0;i<XH;i++)
for(j=0;j<XH;j++)
{
d[i][j] |= (d[i][mi] && d[mi][j]);
}
/*for(i=0;i<n;i++)
for(j=0;j<m;j++){
if(d[PL[j].nu][REC[i].nu]==1){
printf("第%d个设备可以和第%d个插座连接在一起\n",j+1,i+1);
}
}*/
s=0;
/*编号是从1-m(设备的编号),m+1到m+n(插座的编号),0和n+m+1分别代表起点s和终点s
标号从0开始的是边的标号
*/
for(i=1;i<=m;i++) addEdge(s,i,1);
t=n+m+1;
for(i=1;i<=n;i++)addEdge(i+m,t,1);
for(i=0;i<m;i++)
for(j=0;j<n;j++)
if(d[PL[i].nu][REC[j].nu]==1) addEdge(i+1,m+j+1,INF);
/*for(i=0;i<Edges.size();i++){
printf("%d %d %d %d\n",Edges[i].from,Edges[i].to,Edges[i].cap,Edges[i].flow);
}*/
for(;;){
queue<int> Q;
Q.push(s);
memset(aa,0,sizeof(aa));
aa[s]=INF;
while(!Q.empty()){
int x=Q.front(); Q.pop();
for(i=0;i<G[x].size();i++){
edge& e=Edges[G[x][i]];
if(!aa[e.to]&&e.cap>e.flow){
aa[e.to]=min(aa[x],e.cap-e.flow);
p[e.to]=G[x][i];
Q.push(e.to);
}//
}
if(aa[t]) break;
}
if(!aa[t]) break;
for(hs=t;hs!=s;hs=Edges[p[hs]].from){
Edges[p[hs]].flow+=aa[t];
Edges[p[hs]^1].flow-=aa[t];
}
}
cnt=0;
for(i=0;i<m;i++) if(Edges[2*i].flow==1) ++cnt;
if(first) first=0;
else printf("\n");
printf("%d\n",m-cnt);
}
return 0;
}
-------帅气与幽默并存的分割线------
// UVa753 A Plug for UNIX
// Rujia Liu
// 算法一:先做一次floyd,然后再构图
#include<bits/stdc++.h>
using namespace std;
vector<string> names;
int ID(const string& s) {
for(int i = 0; i < names.size(); i++)
if(names[i] == s) return i;
names.push_back(s);
return names.size() - 1;
}
const int maxn = 400 + 5;
int n, m, k; // 插座个数,设备个数,转换器个数
int d[maxn][maxn]; // d[i][j]=1表示插头类型i可以转化为插头类型j
int target[maxn]; // 各个插座的类型
int device[maxn]; // 各个设备的类型
const int INF = 1000000000;
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f):from(u),to(v),cap(c),flow(f) {}
};
struct EdmondsKarp {
int n, m;
vector<Edge> edges; // 边数的两倍
vector<int> G[maxn]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
int a[maxn]; // 当起点到i的可改进量
int p[maxn]; // 最短路树上p的入弧编号
void init(int n) {
for(int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m-2);
G[to].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;
}
};
EdmondsKarp g;
int main() {
int T;
cin >> T;
while(T--) {
names.clear();
string s1, s2;
cin >> n;
for(int i = 0; i < n; i++) {
cin >> s1;
target[i] = ID(s1);
}
cin >> m;
for(int i = 0; i < m; i++) {
cin >> s1 >> s2;
device[i] = ID(s2);
}
cin >> k;
memset(d, 0, sizeof(d));
for(int i = 0; i < k; i++) {
cin >> s1 >> s2;
d[ID(s1)][ID(s2)] = 1;
}
// floyd
int V = names.size(); // 插头类型个数
for(int k = 0; k < V; k++)
for(int i = 0; i < V; i++)
for(int j = 0; j < V; j++)
d[i][j] |= d[i][k] && d[k][j];
g.init(V+2);
for(int i = 0; i < m; i++)
g.AddEdge(V, device[i], 1); // 源点->设备
for(int i = 0; i < n; i++)
g.AddEdge(target[i], V+1, 1); // 插座->汇点
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(d[device[i]][target[j]]) g.AddEdge(device[i], target[j], INF); // 设备->插座
int r = g.Maxflow(V, V+1);
cout << m-r << "\n";
if(T) cout << "\n";
}
return 0;
}