题意:
电脑公司生产电脑有N个机器(N条生产线),每个机器单位时间产量为Qi。 电脑由P个部件组成,每个机器工作时只能把有某些部件的半成品电脑(或什么都没有的空电脑)变成有另一些部件的半成品电脑或完整电脑(也可能移除某些部件)。求电脑公司的单位时间最大产量,以及哪些机器(生产线)有协作关系,即一台机器(一条生产线)把它的产品交给哪些机器(生产线)加工。
样例说明:
3 4
1号生产线: 15 0 0 0 --> 0 1 0
2号生产线: 10 0 0 0 --> 0 1 1
3号生产线: 30 0 1 2 --> 1 1 1
4号生产线: 3 0 2 1 --> 1 1 1
1号生产线能加工 0 0 0 这样的零件(这样的零件也就是无限制零件,源点),它可以把零件加工成 0 1 0 这个样子,然后 3 号生产线可以加工这种零件,并且加工成 1 1 1也就是成品,到这里也就加工成功了,因为1号生产线每次可以加工15个零件,所以 1->3 的加工量就是15,同理可得 2->3 的加工量是 10,所以结果是 25。
题解:
错误的网络流模型:
1) 添加一个源点S,S提供最初的原料 00000...
2) 添加一个汇点T, T接受最终的产品 11111...
3) 将每个机器视为一个节点编号为i, 源点S编号为 0,汇点T编号为 N+1。
4) S 连边到所有接收 "0000..." 或 "若干个0及若干个2" 的机器,容量为无穷大。
5) 若机器A产出的电脑能被机器B接受,则机器A连边到机器B,容量为机器A的产量。
6) 能产出成品 "1111...." 的机器,连边到T,容量无穷大。
7) 求S到T的最大流
初始残量图如下:
最终残量图如下:
若用该网络流模型,第一个样例最终的结果为35,而正确结果为25。从图中得出,2 这台机器超出了它的生产能力。故为错误的网络流模型。
正确的网络流模型:
1) 添加一个源点S,S提供最初的原料 00000...
2) 添加一个汇点T, T接受最终的产品 11111...
3) 将每个机器拆成两个点: 编号为i的接收节点,和编号为i+n的产出节点(n是机器数目),前者用于接收原料,后者用于提供加工后的半成品或成品。这两个点之间要连一条边,容量为单位时间产量Qi
4) S 连边到所有接收 "0000..." 或 "若干个0及若干个2" 的机器,容量为无穷大
5) 产出节点连边到能接受其产品的接收节点,容量无穷大
6) 能产出成品的节点,连边到T,容量无穷大。
7) 求S到T的最大流
AC代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<string.h>
using namespace std;
#define runfile freopen("E:/Code/CB/Root/data.txt", "r", stdin)
#define stopfile fclose(stdin)
#define inf 100000000
#define ll long long
#define maxp 15
struct Node{
int q;
int s[maxp];
int d[maxp];
};
struct node{
int s;
int d;
int flow;
};
const int maxn = 55*2+1;
int g[maxn][maxn],g_copy[maxn][maxn],level[maxn],n,p;
Node ma[maxn];
node b[maxn];
void cre_graph()
{
memset(g, 0 , sizeof(g));
for(int i = 1; i <= n; i++)
{
scanf("%d",&ma[i].q);
g[i][i+n] = ma[i].q;//将一台机器分为两个点,用一条边相连,容量为q
int s1 = 0,d1 = 0;
for(int j = 1; j <= p; j++)
{
scanf("%d",&ma[i].s[j]);
if(ma[i].s[j] == 1)
s1 = 1;
}
//添加一个源点,提供最初始的原材料0000...
//添加一个汇点,接受最终的产品1111...
if(!s1)//如果s[j]不含1,则将其与源点相连,容量为inf
g[0][i] = inf;
for(int k = 1; k <= p; k++)
{
scanf("%d",&ma[i].d[k]);
if(ma[i].d[k] == 0)
d1 = 1;
}
if(!d1)//如果d[j]不含0,则将其与汇点相连,容量为inf
g[i+n][2*n+1] = inf;
}
}
void connect()
{
//将产出节点连边到能接受其产品的接收节点,容量无穷大
int c;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
c = 0;
for(int k = 1; k <= p ;k++)
{
if((ma[i].d[k] == 0 && ma[j].s[k] == 1) || (ma[i].d[k] == 1 && ma[j].s[k] == 0))
break;
else
c++;
}
if(c == p)
g[i+n][j] = inf;
}
}
}
bool BFS()//用BFS来寻找增广路、构建分层图
{
memset(level,-1,sizeof(level));//每次寻找增广路时,都将level数组初始化为-1
level[0] = 0;//起点层次为0
queue<int> q;
q.push(0);
while(!q.empty())
{
int cur = q.front();
q.pop();
for(int i = 0; i <= 2*n+1; i++)
{
if(g[cur][i] && level[i] == -1)//如果当前点与i顶点有边,并且i顶点没有被访问过
{
level[i] = level[cur] + 1;
q.push(i);
}
}
}
if(level[2*n+1] == -1)//如果没有到达汇点,说明没有找到增广路
return false;
return true;
}
int DFS(int s, int MAX)//用DFS来构建残余网络、反向边,计算可行的最大流
{
int a;
if(s == 2*n+1) return MAX;
for(int i = 0; i <= 2*n+1; i++)
{
if(g[s][i] && level[i] == level[s]+1 && (a = DFS(i, min(MAX, g[s][i]))) )//取该路径中的最小残余流量
{
g[s][i] -= a;//构建残余网络
g[i][s] += a;//构建反向边
return a;
}
}
return 0;//如果通过s点到达不了汇点,则返回0
}
ll Dinic()
{
ll ans = 0;
while(BFS())//每找到一条增广路
ans += DFS(0,inf);//从源点出发,初始化最大可行流为无穷大
//结果加上每条增广路得到的最大可行流
return ans;
}
int path()
{
//保存两两机器之间的信息
int k = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(g_copy[i+n][j] > g[i+n][j])
{
b[k].s = i;
b[k].d = j;
b[k].flow = g_copy[i+n][j]-g[i+n][j];
k++;
}
}
}
return k;
}
int main()
{
// runfile;
while(scanf("%d%d",&p,&n) != EOF)
{
cre_graph();
connect();
memcpy(g_copy, g, sizeof(g));
ll maxflow = Dinic();
int num = path();
printf("%lld %d\n",maxflow,num);
for(int i = 0; i < num; i++)
printf("%d %d %d\n",b[i].s,b[i].d,b[i].flow);
}
// stopfile;
return 0;
}