原题大意:
为了追求ACM比赛的公平性,所有用作ACM比赛的电脑性能是一样的,而ACM董事会专门有一条生产线来生产这样的电脑,随着比赛规模的越来越大,生产线的生产能力不能满足需要,所以说ACM董事会想要重新建造一条生产线。
生产线是全自动化的,所以需要机器来组成生产线,给定有多少中种机器,标准ACM用电脑有多少部份,每种机器将什么样的ACM电脑半成品处理成什么样的电脑半成品(对于输入的电脑半成品,每部分有0,1,2三种状态:代表着 0、这部分必须没有我才能处理,1、这部分必须有我才能处理,2、这部分有没有我都能处理。对于输出的电脑半成品有0,1两种状态:代表着0,处理完后的电脑半成品里没有这部分,1、处理完的电脑半成品有这部分),每一个机器每小时可以处理Q个半成品(输入数据中的Qi)。
求电脑公司的单位时间最大产量,以及哪些机器有协作关系,即一台机器把它的产品交给哪些机器加工。(每小时最多生成多少成品,成品的定义就是所有部分的状态都是“1”)
Sample input 1
3 4
15 0 0 0 0 1 0
10 0 0 0 0 1 1
30 0 1 2 1 1 1
3 0 2 1 1 1 1
Sample output 1
25 2
1 3 15
2 3 10
第一行输入两个数:一个P代表有P个零件, 一个 N代表有N台机器。
接下来N行,每行第一个数字有Qi,代表 第i个零件每小时能加工的半成品零件个数。然后是2*P个数字,前P个数字是加工前半成品需要满足的条件,后P个数表示加工后的半成品的数量。
将原题抽象为图的步骤:
(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的最大流
Trick: S 可以连边到222, 020, ….
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int INF=0x3f3f3f3f,maxn=2000;
struct node{
int in[12],out[12];//第i台机器的输入输出规格
int flow;//第i台机器能放出的最大流量
}P[maxn];
int G[maxn][maxn],backupG[maxn][maxn],visited[maxn],Layer[maxn];
int n,m;//n台机器,每台机器需要m个零件
bool OK(int a,int b){
for(int i=0;i<m;i++){
if(!(P[a].out[i]==P[b].in[i]||P[b].in[i]==2))
return false;
}
return true;
}
bool CountLayer(int n){//分层
queue<int>Q;
memset(Layer,0xff,sizeof(Layer));//都初始化成-1
Layer[0]=0;
Q.push(0);
while(!Q.empty()){
int v=Q.front();
Q.pop();
for(int i=0;i<=n;i++){
if(G[v][i]>0&&Layer[i]==-1){
//Layer[i]==-1说明还没有访问过
Layer[i]=Layer[v]+1;
if(i==n) return true;//分层到汇点即可
else Q.push(i);
}
}
}
return false;
}
int Dinic(int n){
int maxFlow=0;
deque<int>Q;//DFS用的栈
while(CountLayer(n)){//只要能分层
Q.push_back(0);//源点入栈
memset(visited,0,sizeof(visited));
visited[0]=1;
while(!Q.empty()){
int nd=Q.back();
if(nd==n){//nd是汇点
//在栈中寻找容量最小的边
int minC=INF;
int minC_vs;//容量最小的边的起点
for(int i=1;i<Q.size();i++){
int vs=Q[i-1];
int ve=Q[i];
if(G[vs][ve]>0){
if(minC>G[vs][ve]){
minC=G[vs][ve];
minC_vs=vs;
}
}
}
//增广,改图
maxFlow+=minC;
for(int i=1;i<Q.size();i++){
int vs=Q[i-1];
int ve=Q[i];
G[vs][ve]-=minC;//修改边容量
G[ve][vs]+=minC;//添加反向边
}
//退栈到minC_vs成为栈顶,以便继续dfs
while(!Q.empty()&&Q.back()!=minC_vs){
visited[Q.back()]=0;
Q.pop_back();
}
}
else{//nd不是汇点
int i;
for(i=0;i<=n;i++){
if(G[nd][i]>0&&Layer[i]==Layer[nd]+1&&!visited[i]){
visited[i]=1;
Q.push_back(i);
break;
}
}
if(i>n) Q.pop_back();
}
}
}
return maxFlow;
}
int main(){
//freopen("1.txt","r",stdin);
while(scanf("%d %d",&m,&n)!=EOF){
memset(G,0,sizeof(G));
memset(P,0,sizeof(P));
for(int i=1;i<=n;i++){
scanf("%d",&P[i].flow);
for(int j=0;j<m;j++)
scanf("%d",&P[i].in[j]);
for(int j=0;j<m;j++)
scanf("%d",&P[i].out[j]);
}
for(int i=0;i<m;i++){
P[0].in[i]=P[0].out[i]=0;
P[n+1].in[i]=P[n+1].out[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) G[i][i+n]=P[i].flow;
else if(OK(i,j)) G[i+n][j]=INF;
}
}
for(int i=1;i<=n;i++){
if(OK(0,i)) G[0][i]=INF;
}
for(int i=1;i<=n;i++){
if(OK(i,n+1)) G[n+i][2*n+1]=INF;
}
memcpy(backupG,G,sizeof(G));
int maxFlow=Dinic(2*n+1);
// for(int i=1;i<=2*n;i++){
// for(int j=1;j<=2*n;j++){
// printf("(%4d,%4d)",backupG[i][j],G[i][j]);
// }
// printf("\n");
// }
int num=0,a[maxn],b[maxn],c[maxn];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) continue;
if(backupG[i+n][j]>G[i+n][j]){
a[num]=i,b[num]=j,c[num]=backupG[i+n][j]-G[i+n][j];
num++;
}
}
}
printf("%d %d\n",maxFlow,num);
for(int i=0; i<num; i++)
printf("%d %d %d\n",a[i],b[i],c[i]);
}
return 0;
}