POJ 3469 解题报告

这道题大家的做法都是最小割,构图也一样:对每个点,从source加一条capacity为Ai的边,再加一条从该边到sink的capacity为Bi的边。对需要data exchange的点,增加一条capacity为m的双向边。至于为什么这样构图,discuss里面有些说明。当然,我也不是特别清楚原理。

由于最小割就是最大流,所以就转化为考验最大流标程效率的问题。我之前的Edmonds-Karp算法在这里TLE了。遂不得不改变标程为dinic。因为dinic看起来很简单。dinic的理解确实简单些,可以看做EK的优化。

最好的dinic解释可以见这里:http://comzyh.com/blog/archives/568/

dinic本身也有很多优化,多路增广是其中之一:http://poj.org/showmessage?message_id=129060。

别的优化如当前边优化,我自己没能理解其必要性,也没有应用。感兴趣的话,这两个链接有很好的实现:

http://www.cnblogs.com/zhsl/archive/2012/12/03/2800092.html

http://www.cs.cmu.edu/~15451/lectures/lec11/dinic.pdf

thestoryofsnow3469Accepted6016K3782MSC++5140B

/* 
ID: thestor1 
LANG: C++ 
TASK: poj3469 
*/
#include <iostream>
#include <fstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <algorithm>
#include <cassert>

using namespace std;

const int MAXN = 20000 + 2;
const int MAXM = 200000;

class Edge{
public:
	// edge (`u` -> `v`) with capacity `w`
	int v, w;
	// use (pre-allocated) array as linked list here
	// thus we need `next` point to next edge in adjacent list of node `u`
	int next;
	// the reverse edge (from `v` -> `u`) should be (edgeno ^ 1)
	// as we are adding edges sequentially
	// for convenience in updating residual graph 

	Edge()
	{
		this->v = -1;
		this->w = -1;
		this->next = -1;
	}
};


// construct level graph(GL) using BFS
// return true if sink is reachable from source (thus there is GL)
// return false otherwise
bool BFS(int GL[], int Queue[], int adjs[], Edge edges[], const int N, const int source, const int sink)
{	
	// initialize all levels as negative(-1)
	memset(GL, -1, N * sizeof(int));
	GL[source] = 0;
	
	int front = 0, rear = 0;
	Queue[0] = source;

	while (front <= rear)
	{
		int u = Queue[front];
		front++;
		int e = adjs[u];
		while (e >= 0)
		{
			int v = edges[e].v;
			// not set before (GL in this case can be thought as visited[])
			if (GL[v] < 0 && edges[e].w > 0)
			{
				GL[v] = GL[u] + 1;
				rear++;
				assert (rear < N);
				Queue[rear] = v;
			}

			// set as next node in adjacent list
			e = edges[e].next;
		}
	}

	if (GL[sink] > 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int DFS(int u, int low, int GL[], int adjs[], Edge edges[], const int sink)
{
	// if reached sink, then return the narrowest capacity in the path (low)
	if (u == sink || !low)
	{
		return low;
	}

	// otherwise recursively check each adjacent edge (thus reach next node)
	int total = 0;
	int e = adjs[u];
	while (e >= 0 && low)
	{
		int v = edges[e].v;
		// v is at next level in level graph
		if (GL[v] == GL[u] + 1 && edges[e].w > 0)
		{
			int flow = DFS(v, min(low, edges[e].w), GL, adjs, edges, sink);
			// if can reach sink (because otherwise `flow` will be 0)
			if (flow > 0)
			{
				// update residual graph
				edges[e].w -= flow;
				edges[e ^ 1].w += flow;
				low -= flow;
				total += flow;
			}
		}

		e = edges[e].next;	
	}

	// optimization, no need to try this node any more if it has no outgoing flows
	if (total == 0)
	{
		GL[u] = -1;
	}

	return total;
}

// for more about dinic: http://comzyh.com/blog/archives/568/
int dinic(int GL[], int Queue[], int adjs[], Edge edges[], const int N, const int source, const int sink)
{
	int maxflow = 0;
	// construct level graph G_L using BFS
	// until sink cannot be reached from source
	while (BFS(GL, Queue, adjs, edges, N, source, sink))
	{
		// search a blocking flow (augment path) using DFS
		
		// int flow = DFS(source, numeric_limits<int>::max(), GL, adjs, edges, sink);
		// while (flow > 0)
		// {
		// 	maxflow += flow;
		// 	flow = DFS(source, numeric_limits<int>::max(), GL, adjs, edges, sink);
		// }

		maxflow += DFS(source, numeric_limits<int>::max(), GL, adjs, edges, sink);
	}

	return maxflow;
}

int main()
{
	Edge edges[2 * MAXN + 2 * MAXM + 2 * MAXN];
	int edgeno = 0;

	int adjs[MAXN];
	// int current[MAXN];

	int GL[MAXN];
	int Queue[MAXN];

	int N, M;
	scanf("%d%d", &N, &M);
	
	memset(adjs, -1, (N + 2) * sizeof(int));

	for (int i = 0; i < N; ++i)
	{
		int Ai, Bi;
		scanf("%d%d", &Ai, &Bi);

		// 0 -> i + 1, capacity: Ai
		edges[edgeno].v = i + 1;
		edges[edgeno].w = Ai;
		edges[edgeno].next = adjs[0];
		adjs[0] = edgeno;
		edgeno++;

		// i + 1 -> 0, capacity: 0
		edges[edgeno].v = 0;
		edges[edgeno].w = 0;
		edges[edgeno].next = adjs[i + 1];
		adjs[i + 1] = edgeno;
		edgeno++;

		// i + 1 -> N + 1, capacity: Bi
		edges[edgeno].v = N + 1;
		edges[edgeno].w = Bi;
		edges[edgeno].next = adjs[i + 1];
		adjs[i + 1] = edgeno;
		edgeno++;

		// N + 1 -> i + 1, capacity: 0
		edges[edgeno].v = i + 1;
		edges[edgeno].w = 0;
		edges[edgeno].next = adjs[N + 1];
		adjs[N + 1] = edgeno;
		edgeno++;
	}

	for (int i = 0; i < M; ++i)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);

			// a -> b, capacity: w
		edges[edgeno].v = b;
		edges[edgeno].w = w;
		edges[edgeno].next = adjs[a];
		adjs[a] = edgeno;
		edgeno++;

			// b -> a, capacity: w
		edges[edgeno].v = a;
		edges[edgeno].w = w;
		edges[edgeno].next = adjs[b];
		adjs[b] = edgeno;
		edgeno++;
	}

	assert(edgeno <= 2 * MAXN + 2 * MAXM + 2 * MAXN);

	// printf("[debug]edges(%d):\n", edgeno);
	// for (int i = 0; i < edgeno; ++i)
	// {
	// 	printf("i: %d, v: %d, w: %d, next: %d, reverse: %d\n", i, edges[i].v, edges[i].w, edges[i].next, edges[i].reverse);
	// }

	N += 2;
	const int source = 0, sink = N - 1;
	printf("%d\n", dinic(GL, Queue, adjs, edges, N, source, sink));

	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ1753题目为"Flip Game",题目给出了一个4x4的棋盘,每个格子有黑色或白色,每次翻转一个格子会同时翻转它上下左右四个格子的颜色,目标是把整个棋盘都变为同一种颜色,求把棋盘变成同种颜色的最小步数。 解题思路: 一般关于棋盘变色的题目,可以考虑使用搜索来解决。对于POJ1753题目,可以使用广度优先搜索(BFS)来解决。 首先,对于每个格子,定义一个状态,0表示当前格子是白色,1表示当前格子是黑色。 然后,我们可以把棋盘抽象成一个长度为16的二进制数,将所有格子的状态按照从左往右,从上往下的顺序排列,就可以用一个16位的二进制数表示整个棋盘的状态。例如,一个棋盘状态为: 0101 1010 0101 1010 则按照从左往右,从上往下的顺序把所有格子的状态连接起来,即可得到该棋盘的状态为"0101101001011010"。 接着,我们可以使用队列来实现广度优先搜索。首先将初始状态加入队列中,然后对于队列中的每一个状态,我们都尝试将棋盘上的每个格子翻转一次,生成一个新状态,将新状态加入队列中。对于每一个新状态,我们也需要记录它是从哪个状态翻转得到的,以便在得到最终状态时能够输出路径。 在搜索过程中,我们需要维护每个状态离初始状态的步数,即将该状态转换为最终状态需要的最小步数。如果我们找到了最终状态,就可以输出答案,即最小步数。 代码实现:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值