题目链接:http://poj.org/problem?id=3436
题目意思:
一台电脑有P个部分(P不超过10),总共有N台生产电脑的机器可以对电脑(添加或拆除一些组件)。
每台机器的工作效率为Q,给出N台机器可接收的电脑的情况,和输出电脑的情况。
则给出P个数:0代表这个部分不能有,1代表这个部分一定要有 ,2代表这个部分可有可无
然后在给出P个数:是其出产的电脑的情况,0代表缺少这个部分,1代表已安装这个部分。
只有P个数全为1才是成品。
最终求最大产量:以及那些机器有合作关系。
就是求最大流和构成最大流的那些边。
解题思路:
首先建立超级源点S和超级汇点D,然后S对于那些接收电脑不需要任何组件的机器建立关系。
D与那些能够生产出成品的机器建立关系。对于同一个机器拆成两点,其流量为该机器的产量。
也有不拆点做的人,也是弄超级源点和超级汇点,如果机器i所产的产品能被机器j加工,则建立
i到j的边,其流量是i,j两个机器中最小的产量。但是这中做法虽然能够AC,这是因为POJ测试
数据不全面,原来我不太明白为什么要拆点,看了别人不拆点的思路,我觉得挺有道理,但是
看到一组测试数据,我就彻底明白了为啥要拆点,感觉最大的作用就是限制了流量。
比如下面这个数据:
2 4
10 0001
10 0000
10 0111
10 0111
不拆点写法答案:20
拆点写法答案:10
正确答案:10
按照那些不拆点人的想法,建图如下:
可见这样执行完算法,求解出答案的确是20,这些机器是同时工作的,就像平时那个流水线一样同步
并行这样的结果必然是不可能出现的实际上。
所以就来看看拆点的写法所构建的图:
所以这个题目拆点来写才是正解。从图中可以看出除了一个机器拆出的两点之间的流量是自身的产量,
其他边的流量都使用INF来表示。
题目还让求出那些边有输送关系。我们可以先保留原图,然后备份原图,在备份的图上计算最大流量,
则备份的图最终留下一个残余网络,则原网络-残余网络大于0的边,二者之间有产品的输送关系。
输送量为两者之差。
EK算法AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 110;
int N,P; ///N台部件和P个部分
int S,D; ///超级源点和超级汇点
int cap1[maxn][maxn]; ///在原图上进行求取最大流的操作
int cap2[maxn][maxn]; ///原图备份的图
int pre[maxn]; ///前驱节点
int yield[maxn]; ///每台机器的产量
int input[maxn][15]; ///每台机器可以接收的电脑的情况
int output[maxn][15]; ///每台机器可以输出的电脑的情况
int minFlow[maxn];
int maxFlow;
void Edmonds_Karp()
{
int u,v;
maxFlow = 0;
queue<int>qu;
while(true)
{
memset(minFlow,0,sizeof(minFlow));
minFlow[S] = INF+1;
qu.push(S);
while(!qu.empty())
{
u = qu.front();
qu.pop();
for(v = 0; v <= 2*N+1; v++)
{
if(minFlow[v]==0 && cap1[u][v] != 0)
{
pre[v] = u;
qu.push(v);
minFlow[v] = min(minFlow[u],cap1[u][v]); ///在此过程中就把S到D路径中的最小流量求出
}
}
}
if(minFlow[D]==0) ///不能找到一条S到D的增广路径了
break;
maxFlow += minFlow[D];
for(v = D; v != S; v = pre[v])
{
u = pre[v];
cap1[u][v] -= minFlow[D];
cap1[v][u] += minFlow[D];
}
}
}
int main()
{
while(~scanf("%d%d",&P,&N))
{
for(int i = 1; i <= N; i++)
{
scanf("%d",&yield[i]);
input[i][0] = 1; ///代表改机器接受的电脑不必需包括任一组件
for(int j = 1; j <= P; j++)
{
scanf("%d",&input[i][j]);
if(input[i][j] == 1)
input[i][0] = 0; ///代表该机器接收的电脑必须包含某一组件
}
output[i][0] = 1; ///代表该机器输出的是成品
for(int j = 1; j <= P; j++)
{
scanf("%d",&output[i][j]);
if(output[i][j] != 1)
output[i][0] = 0; ///代表该机器输出的不是成品。
}
}
memset(cap1,0,sizeof(cap1)); ///原来的图
memset(cap2,0,sizeof(cap2)); ///复制的图。
S = 0;
D = 2*N+1;
for(int i = 1; i <= N; i++)
{
cap1[i][i+N] = cap2[i][i+N] = yield[i]; ///一个点拆成两个点,建立一条边,其流量为该机器的效率。
if(input[i][0] == 1) ///建立0到i的边
{
cap1[S][i] = cap2[S][i] = INF;
}
///在此拆点,第i台机器拆成输入、输出两点,编号分别为i,i+N,代表第i台机器可以输出成品,与D建立关系
if(output[i][0] == 1)
{
cap1[i+N][D] = cap2[i+N][D] = INF;
}
}
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++)
{
if(i != j)
{
bool canConnected = true; ///设i到j可以建边
for(int k = 1; k <= P; k++)
{
if(output[i][k] + input[j][k] == 1)
{
canConnected = false;
break;
}
}
if(canConnected == true)
{
cap1[i+N][j] = cap2[i+N][j] = INF;
}
}
}
Edmonds_Karp(); ///求最大流后,cap1就是最终残余网络
///求解最大流中每条边的流量。
///用原图的备份图减去残余网络上的剩余量。
int ansfrom[maxn];
int ansto[maxn];
int ansyield[maxn];
int cnt = 0;
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++)
{
if(i != j)
{
///残余网络 原网络备份
if(cap1[i+N][j] < cap2[i+N][j])
{
ansfrom[cnt] = i;
ansto[cnt] = j;
ansyield[cnt++] = cap2[i+N][j]-cap1[i+N][j];
}
}
}
printf("%d %d\n",maxFlow,cnt);
for(int i = 0; i < cnt; i++)
{
printf("%d %d %d\n",ansfrom[i],ansto[i],ansyield[i]);
}
}
return 0;
}
Dinic算法实现:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 110;
int N,P; ///N台部件和P个部分
int S,D; ///超级源点和超级汇点
int cap1[maxn][maxn]; ///在原图上进行求取最大流的操作
int cap2[maxn][maxn]; ///原图备份的图
int yield[maxn]; ///每台机器的产量
int input[maxn][15]; ///每台机器可以接收的电脑的情况
int output[maxn][15]; ///每台机器可以输出的电脑的情况
int maxFlow,minFlow,minFlowNode;
int level[maxn]; ///给残余网络的点进行层次编号
int vis[maxn];
///bfs对残余网络中点的层次进行编号
bool bfs()
{
queue<int>qu;
memset(level,-1,sizeof(level));
level[S] = 0;
qu.push(S);
while(!qu.empty())
{
int u = qu.front();
qu.pop();
for(int i = 0; i <= 2*N+1; i++)
{
///节点i还没有被编号,且u到i还有流量
if(level[i]==-1 && cap1[u][i]>0)
{
level[i] = level[u] + 1;
/**如果到了D,直接结束就可以了,因为按照这个算法,
再往后走即便又回到D又有什么意义,编号还更深了*/
if(i == D)
return true;
qu.push(i);
}
}
}
return false; ///代表bfs分层不能成功找到终点
}
///再成功分层的情况下再进行dfs
void dfs()
{
int u,v,cur,i;
deque<int>qu; ///双端队列,当作我们用的栈
memset(vis,0,sizeof(vis)); ///设置所有点都没有访问过
qu.push_back(S);
vis[S] = 1;
while(!qu.empty())
{
cur = qu.back(); ///返回最后一个元素
if(cur == D) ///如果到达终点,栈中存放的点是S到D的一条增广路径
{
///找一路走来容量最小的边,以及该边的起点。
minFlow = INF+1;
minFlowNode = S;
for(i = 1; i < qu.size(); i++)
{
u = qu[i-1];
v = qu[i];
if(cap1[u][v] > 0 && minFlow>cap1[u][v])
{
minFlow = cap1[u][v];
minFlowNode = u;
}
}
///加上增加的流量。
maxFlow += minFlow;
for(i = 1; i < qu.size(); i++)
{
u = qu[i-1];
v = qu[i];
cap1[u][v] -= minFlow;
cap1[v][u] += minFlow;
}
while(!qu.empty() && qu.back() != minFlowNode)
{
vis[qu.back()] = 0;
qu.pop_back();
}
}
else
{
for(i = 1; i <= 2*N+1; i++)
{
///cur到i的流量,到更深一层找一个未访问的节点。
if(cap1[cur][i]>0 && level[i] == level[cur]+1 && vis[i]==0)
{
vis[i] = 1;
qu.push_back(i);
break;
}
}
if(i > 2*N+1) ///找不到
qu.pop_back();
}
}
}
void dinic()
{
maxFlow = 0;
while(bfs()) ///如果bfs还能分层最终找到D点
{
dfs();
}
}
int main()
{
while(~scanf("%d%d",&P,&N))
{
for(int i = 1; i <= N; i++)
{
scanf("%d",&yield[i]);
input[i][0] = 1; ///代表改机器接受的电脑不必需包括任一组件
for(int j = 1; j <= P; j++)
{
scanf("%d",&input[i][j]);
if(input[i][j] == 1)
input[i][0] = 0; ///代表该机器接收的电脑必须包含某一组件
}
output[i][0] = 1; ///代表该机器输出的是成品
for(int j = 1; j <= P; j++)
{
scanf("%d",&output[i][j]);
if(output[i][j] != 1)
output[i][0] = 0; ///代表该机器输出的不是成品。
}
}
memset(cap1,0,sizeof(cap1)); ///原来的图
memset(cap2,0,sizeof(cap2)); ///复制的图。
S = 0;
D = 2*N+1;
for(int i = 1; i <= N; i++)
{
cap1[i][i+N] = cap2[i][i+N] = yield[i]; ///一个点拆成两个点,建立一条边,其流量为该机器的效率。
if(input[i][0] == 1) ///建立0到i的边
{
cap1[S][i] = cap2[S][i] = INF;
}
///在此拆点,第i台机器拆成输入、输出两点,编号分别为i,i+N,代表第i台机器可以输出成品,与D建立关系
if(output[i][0] == 1)
{
cap1[i+N][D] = cap2[i+N][D] = INF;
}
}
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++)
{
if(i != j)
{
bool canConnected = true; ///设i到j可以建边
for(int k = 1; k <= P; k++)
{
if(output[i][k] + input[j][k] == 1)
{
canConnected = false;
break;
}
}
if(canConnected == true)
{
cap1[i+N][j] = cap2[i+N][j] = INF;
}
}
}
///求解最大流中每条边的流量。
///用原图的备份图减去残余网络上的剩余量。
dinic();
int ansfrom[maxn];
int ansto[maxn];
int ansyield[maxn];
int cnt = 0;
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++)
{
if(i != j)
{
///残余网络 原网络备份
if(cap1[i+N][j] < cap2[i+N][j])
{
ansfrom[cnt] = i;
ansto[cnt] = j;
ansyield[cnt++] = cap2[i+N][j]-cap1[i+N][j];
}
}
}
printf("%d %d\n",maxFlow,cnt);
for(int i = 0; i < cnt; i++)
{
printf("%d %d %d\n",ansfrom[i],ansto[i],ansyield[i]);
}
}
return 0;
}