POJ3436-ACM Computer Factory

全解题报告索引目录 -> 【北大ACM – POJ试题分类

转载请注明出处:http://exp-blog.com

-------------------------------------------------------------------------

 

 

 

提示:最大流问题

     折磨了我3天的题。。。网上的前辈都推荐拆点做,但是我没有用拆点(感觉拆点很麻烦)

        这道题我用了三种方法去做,但是结果却差强人意。。。。

         【BFS+标号法+不拆点】 成功AC

         【BFS+压入重标法+不拆点】(WA,不知道错哪里了,找不到反例) 

         【BFS+压入重标法+模拟拆点】(WA,不知道错哪里了,找不到反例)     

AC的程序我贴下面,后两个WA的代码我贴在AC代码下面,希望有达人帮我查出哪里出错了。。。无限感激

 

题意:

老实说,我完全看不懂题目在说什么= =。。。Orz

不过还是简单概括下:

有N台机器,每台机器有P部分,每部分都有各自的输入、输出规格,因此一台机器有P个输入规格,P个输出规格。每台机器有2*P+1种参数去描述:第一个参数Q:该机器的容量;接下来P个参数S:该机器各部分的输入规格;接下来P个参数D:该机器各部分的输出规格。

其中输入规格有三种情况:0,1,2

0:该部分不能存在

1:该部分必须保留

2:该部分可有可无

输出规格有2种情况:0,1

0:该部分不存在

1:该部分存在

 

至于这条题要我做什么,我完全不理解= =

不过通过对样例的输入输出的剖析,再加之前人总结出来的一些不清不楚的见解,我能够详尽地分析这题的模型O(∩_∩)O哈哈~

 

注意:本题可以只有一次输入,一次输出,还有Sample I/O这段英文不用输入输出

Sample input:

P  N (N台机器,每台机器有P部分)

接着输入N行,其实每行都是一个结点的信息

每一行的格式为 一个Q  P个S  P个D

其中Q为当前结点的容量,S都是当前结点的输入规格,D都是输出规格

Sample output:

第一行的两个数字分别表示:最大流的值,流量发生变化的边数M(和s还有t关联的边不在其内,那些不属于原有的边,是附加边)

接下来有M行,每一行都有三个数字,A B W

A B为流量发生变化的边的端点,W为流量的变化值(每条边初始流量为0,最终流量就是找到最大流时的流量)

 

若图不连通,则输出0 0

 

解题思路:

 首先构造图:

添加两个超级源s,超级汇t

  如果某个节点(i)的输入部分不含1,则添加一条s->i路径,容量为Qi;

  如果某个节点(j)输出全为1,则添加一条j->t路径,容量为Qj;

  如果节点i的输出与j的输入不存在冲突(输出与输入对应位置的和不能为1),则添加一条i->j的路径,容量为min(Qi, Qj).

PS:输出与输入对应位置的和不能为1,就是说组合为可以为00,11, 21或20,但不能是01

解题方法:

就是最大流问题

 

/*BFS+不拆点 -> 成功AC*/

//Memory Time 
//292K   0MS 

#include<iostream>
using namespace std;

const int inf=10001;
int s; //超级源
int t;   //超级汇

int n;  //总结点数(包括超级源、超级汇)
int p;  //每台机器的部分数
int cap[52][52];// 边容量


int min(int a,int b)
{
	return a<b?a:b;
}

/*利用BFS找增广链求网络最大流*/

int maxflow(void)  
{
	int queue[52];
	int head,tail;
	int pre[52]; //结点i的前驱
    
	int minflow;
	int flow = 0;
    int x,y;

    while(true)
    {
        memset(pre, -1, sizeof(pre));

        for(queue[head=tail=0]=s;head<=tail;head++)
        {
            x=queue[head];
            for(int i=0;(i<n) && (pre[t]==-1);i++)//当汇点还没有被标记时
               if (cap[x][i]>0 && pre[i]==-1)  //当结点u指向i的边存在,且i还没有标记前驱时
               {
                    pre[i]=x;//记录结点i的前驱为u
                    queue[++tail]=i;
               }
        }

        if(pre[t]==-1)
			break;//BFS后汇点没有被标记,则跳出while,已经不存在增广链

        minflow=inf;//初始化
        for(x=pre[y=t];y!=s;)//回溯
		{
			if(cap[x][y] < minflow)
				minflow=cap[x][y];//寻找当前增广链中最小容量的边,记录其边权(容量)
			y=x;
			x=pre[y];
		}

        for(x=pre[y=t];y!=s;) //当前增广链 流量调整
		{
			cap[x][y] -= minflow;  //正向弧容量减少
			cap[y][x] += minflow;  //反向弧容量增加
			y=x;
			x=pre[y];
		}

        flow += minflow;  //最大流=每次寻得的增广链的调整量之和
    }
    return flow;//返回最大流
}

int main(int i,int j,int k)
{
	int in[52][21];
	int out[52][3];
	int backup[52][52];//备份图
    int N;  //除超级源、超级汇以外的总结点数
	int line;  //生产线数(容量发生变化的边数)
	int flow;  //最大流

    while (cin>>p>>N)
    {
		/*Initial*/

        memset(cap,0,sizeof(cap)); //所有正向弧和反向弧的容量都初始化为0

        s=0;//超级源
		t=N+1; //超级汇
        n=N+2; //总结点数+2
		line=0;  //记录变化的边的数量(生产线数量)

		/*Input*/

        for(i=1;i<=N;i++)
            for(j=0;j<2*p+1;j++)
                cin>>in[i][j];    //用一个数列存储第i个结点的信息 in[i][0] 为结点i的容量
        
		bool flag_s, flag_t;
        for(i=1;i<=N;i++)
        {
            flag_s=flag_t=true;
            for(k=0;k<p;k++)
            {
                if(in[i][1+k]==1)
					flag_s=false;  //检查第i个结点的输入序列信息,当输入列不含1时
                if(in[i][p+1+k]==0)
					flag_t=false;//检查第i个结点的输出序列信息,当输出列全为1时
            }
            if(flag_s)
				cap[s][i]=in[i][0];  //当输入列不含1时,S->i,边容量为i的容量
            if(flag_t)
				cap[i][t]=in[i][0]; //当输出列全为1时,i->t,边容量为i的容量

            bool flag=true;
            for(j=1;j<=N;j++)
				if(i!=j)
                {
                    flag=true;
                    for(k=0;(k<p) && flag;k++)
						if(in[i][p+1+k]+in[j][1+k]==1)  //当第i个结点的第k个输出位,对应第j个结点的第k个输入位之和全不为0时
                            flag=false;

                    if(flag)
						cap[i][j] = min(in[i][0], in[j][0]);  //i->j,边容量为i的容量和j的容量的最小值
                }
        }

		/*利用BFS找增广链求网络最大流*/

        memcpy(backup, cap, sizeof(cap));  //把寻找增广链前的图的容量信息复制
        flow=maxflow();  //返回最大流
        
		/*Output*/

        for(i=1;i<=N;i++)   //注意范围,排除了含超级源和超级汇的边
			for(j=1;j<=N;j++)
                if (cap[i][j] < backup[i][j])//比较调整前后的边权,若容量减少了,则输出这条边的信息
                {
                    out[line][0]=i;     //i,j为生产线的两端点
                    out[line][1]=j;
                    out[line][2]=backup[i][j] - cap[i][j];//变化的流量值(该生产线的最大生产量)
                    line++;
                }

        cout<<flow<<' '<<line<<endl;
        for(i=0;i<line;i++)
            cout<<out[i][0]<<' '<<out[i][1]<<' '<<out[i][2]<<endl;
    }
    return 0;
}


 

 

 

==========华丽的分割线===========

/*【BFS+压入重标法+不拆点】-> WA */

#include<iostream>
using namespace std;

const int inf=10001;
int s=0; //超级源
int t;   //超级汇

int n;  //机器数
int p;  //每台机器的部分数
int cap[52][52];  //弧(i,j)的容量
int flow[52][52];  //弧(i,j)的流量
bool mark[52][52]={false};  
int sum=0;
bool vist[52];   //标记点i是否已标号

class info   //当前点j的标记信息
{
public:
	int pre;  //当前点j的前驱i
	int lv;  //l(v)
	int q;  //机器(结点i)的生产量(容量)
	int in[10];  //输入规格
	int out[10]; //输出规格
	int nei[51];  //当前结点直接指向的邻居结点
	int pn;  //邻居结点的指针
}node[52];

int min(int a,int b)
{
	return a<b?a:b;
}

void back(void)
{
	int x=t;
	while(x!=s)
	{
		if(x!=t && node[x].pre!=s)
		{
			if(!mark[ node[x].pre ][x])
				sum++;           //记录流量发生变化的弧数(含s、t的弧除外)
			mark[ node[x].pre ][x]=true;  //标记弧(i,j)的流量是否发生了变化(含s、t的弧除外)
		}
		flow[ node[x].pre ][x] += node[t].lv;  //改进增广路
		x=node[x].pre;

	}
	return;
}

bool bfs(void)
{
	memset(vist,false,sizeof(vist));
	vist[s]=true;

	int queue[52];
	int head=0;
	int tail=0;
	queue[tail++]=s;

	while(head<=tail-1)  //注意,这是再也找不到增广路的结束条件
	{
		int x=queue[head];
		int y;
		for(int i=0;i<node[x].pn;i++)
		{
			y=node[x].nei[i];
			if(!vist[y] && flow[x][y]<cap[x][y])   //搜索的目标要求是 未标记 & 非饱和弧
			{
				queue[tail++]=y;

				vist[y]=true;
				node[y].pre=x;
				node[y].lv=min( node[x].lv , cap[x][y]-flow[x][y] );
			}
			if(vist[t])  //当超级汇被标记
				break;
		}
		if(!vist[t])
			head++;
		else
			return true;  //搜索到一条增广路
	}
	return false;
}

int main(int i,int j,int k)
{
	/*Input*/

	cin>>p>>n;

	/*Initial*/

	node[s].pre=-1;
	node[s].lv=inf;
	t=n+1;
	for(i=0;i<t;i++)
		node[i].pn=0;

	/*Input & Structure Graphs*/

	bool sign;
	for(i=1;i<=n;i++)
	{
		sign=false;
		cin>>node[i].q;

		for(j=0;j<p;j++)
		{
			cin>>node[i].in[j];
			if(node[i].in[j]==1)  //如果某个节点(i)的输入部分不含1
				sign=true;
		}
		if(!sign)   //则添加一条s->i路径,容量为Qi
		{
			node[s].nei[ node[s].pn++ ]=i;
			cap[s][i]=node[i].q;
			flow[s][i]=0;
		}

		sign=false;
		for(j=0;j<p;j++)
		{
			cin>>node[i].out[j];
			if(node[i].out[j]==0)  //如果某个节点(j)输出全为1
				sign=true;
		}
		if(!sign)  //则添加一条j->t路径,容量为Qj
		{
			node[i].nei[ node[i].pn++ ]=t;
			cap[i][t]=node[i].q;
			flow[i][t]=0;
		}
	}

	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
		{
			sign=false;
			if(i!=j)
			{
				for(k=0;k<p;k++)
					if((node[i].out[k] + node[j].in[k])==1)  //如果节点i的输出与j的输入不存在冲突
					{                                      //即输出与输入对应位置的和不为1
						sign=true;
						break;
					}

				if(!sign)        //则添加一条i->j的路径,容量为min(Qi, Qj).
				{
					node[i].nei[ node[i].pn++ ]=j;
					cap[i][j]=min(node[i].q,node[j].q);
					flow[i][j]=0;
				}
			}
		}

	/*压入重标法找增广轨*/

	while(true)
	{
		if(bfs())  //如果能搜到到增广路
			back();  //从超级汇开始回溯,改进增广路
		else
		{
			int max=0;
			for(i=0;i<node[s].pn;i++)
				max+=flow[s][ node[s].nei[i] ];
			cout<<max<<' '<<sum<<endl;
			for(i=1;i<=n;i++)
				for(j=1;j<=n;j++)
					if(i!=j && mark[i][j])
						cout<<i<<' '<<j<<' '<<flow[i][j]<<endl;
			break;
		}
	}
	return 0;
}


 

 

==========华丽的分割线===========

/*【BFS+压入重标法+模拟拆点】-> WA */


#include<iostream>
using namespace std;

const int inf=10001;
int s=0; //超级源
int t;   //超级汇

int n;  //机器数
int p;  //每台机器的部分数
int cap[52][52];  //弧(i,j)的容量
int flow[52][52];  //弧(i,j)的流量
bool mark[52][52]={false};  
int sum=0;
bool vist[52];   //标记点i是否已标号

class info   //当前点j的标记信息
{
public:
	int pre;  //当前点j的前驱i
	int lv;  //l(v)
	int q;  //机器(结点j)的总生产量(容量)
	int f;  //机器(结点j)的当前生产量(流量)
	int in[10];  //输入规格
	int out[10]; //输出规格
	int nei[51];  //当前结点直接指向的邻居结点
	int pn;  //邻居结点的指针
}node[52];

int min(int a,int b)
{
	return a<b?a:b;
}

void back(void)
{
	int x=t;
	while(x!=s)
	{
		if(x!=t && node[x].pre!=s)
		{
			if(!mark[ node[x].pre ][x])
				sum++;           //记录流量发生变化的弧数(含s、t的弧除外)
			mark[ node[x].pre ][x]=true;  //标记弧(i,j)的流量是否发生了变化(含s、t的弧除外)
		}
		flow[ node[x].pre ][x] += node[t].lv;  //改进增广路
		node[x].f += node[t].lv;        //改进增广路上的顶点
		x=node[x].pre;

	}
	return;
}

bool bfs(void)
{
	memset(vist,false,sizeof(vist));
	vist[s]=true;

	int queue[52];
	int head=0;
	int tail=0;
	queue[tail++]=s;

	while(head<=tail-1)  //注意,这是再也找不到增广路的结束条件
	{
		int x=queue[head];
		int y;
		for(int i=0;i<node[x].pn;i++)
		{
			y=node[x].nei[i];
			if(!vist[y] && flow[x][y]<cap[x][y] && node[y].f<node[y].q)   //搜索的目标要求是 未标记 & 非饱和弧 & 非饱和点(模拟拆点)
			{                                                             //当某一顶点满流后,该顶点不能再生产更多的机器
				queue[tail++]=y;

				vist[y]=true;
				node[y].pre=x;
				node[y].lv=min( node[x].lv , cap[x][y]-flow[x][y] );
			}
			if(vist[t])  //当超级汇被标记
				break;
		}
		if(!vist[t])
			head++;
		else
			return true;  //搜索到一条增广路
	}
	return false;
}

int main(int i,int j,int k)
{
	freopen("in.txt","r",stdin);

	/*Input*/

	cin>>p>>n;

	/*Initial*/
 
	t=n+1;
	node[s].pre=-1;
	node[s].lv=inf;
	node[t].q=inf;
	for(i=0;i<=t;i++)
	{
		node[i].pn=0;
		node[i].f=0;
	}

	/*Input & Structure Graphs*/

	bool sign;
	for(i=1;i<=n;i++)
	{
		sign=false;
		cin>>node[i].q;

		for(j=0;j<p;j++)
		{
			cin>>node[i].in[j];
			if(node[i].in[j]==1)  //如果某个节点(i)的输入部分不含1
				sign=true;
		}
		if(!sign)   //则添加一条s->i路径,容量为Qi
		{
			node[s].nei[ node[s].pn++ ]=i;
			cap[s][i]=node[i].q;
			flow[s][i]=0;
		}

		sign=false;
		for(j=0;j<p;j++)
		{
			cin>>node[i].out[j];
			if(node[i].out[j]==0)  //如果某个节点(j)输出全为1
				sign=true;
		}
		if(!sign)  //则添加一条j->t路径,容量为Qj
		{
			node[i].nei[ node[i].pn++ ]=t;
			cap[i][t]=node[i].q;
			flow[i][t]=0;
		}
	}

	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
		{
			sign=false;
			if(i!=j)
			{
				for(k=0;k<p;k++)
					if((node[i].out[k] + node[j].in[k])==1)  //如果节点i的输出与j的输入不存在冲突
					{                                      //即输出与输入对应位置的和不为1
						sign=true;
						break;
					}

				if(!sign)        //则添加一条i->j的路径,容量为min(Qi, Qj).
				{
					node[i].nei[ node[i].pn++ ]=j;
					cap[i][j]=min(node[i].q,node[j].q);
					flow[i][j]=0;
				}
			}
		}

	/*压入重标法找增广轨*/

	while(true)
	{
		if(bfs())  //如果能搜到到增广路
			back();  //从超级汇开始回溯,改进增广路
		else
		{
			int max=0;
			for(i=0;i<node[s].pn;i++)
				max+=flow[s][ node[s].nei[i] ];
			cout<<max<<' '<<sum<<endl;
			for(i=1;i<=n;i++)
				for(j=1;j<=n;j++)
					if(i!=j && mark[i][j])
						cout<<i<<' '<<j<<' '<<flow[i][j]<<endl;
			break;
		}
	}
	return 0;
}


 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值