BZOJ-3171-循环格-TJOI2013-费用流

本文介绍了一种通过修改最少数量的元素使循环网格达到完美状态的算法。利用拆点建图的方法,结合最小成本最大流模型,实现了路径优化。适用于需要每个节点恰好经过一次的应用场景。

描述

一个循环格就是一个矩阵,其中所有元素为箭头,指向相邻四个格子。每个元素有一个坐标(行,列),其中左上角元素坐标为(0,0)。给定一个起始位置(r,c)

,你可以沿着箭头防线在格子间行走。即如果(r,c)是一个左箭头,那么走到(r,c-1);如果是右箭头那么走到(r,c+1);如果是上箭头那么走到(r-1,c);如果是下箭头那么走到(r+1,c);每一行和每一列都是循环的,即如果走出边界,你会出现在另一侧。

一个完美的循环格是这样定义的:对于任意一个起始位置,你都可以i沿着箭头最终回到起始位置。如果一个循环格不满足完美,你可以随意修改任意一个元素的箭头直到完美。给定一个循环格,你需要计算最少需要修改多少个元素使其完美。


分析

  • 和星际竞速那道题有相通之处. 当时做星际竞速时总结的建模方法在这里用到了.
  • 凡是遇到使每个点都经过一次的题目就可以考虑拆点建图. 拆点, S向Xi连一条容量为1费用为0的边, 表示从X出发. Yi向T连一条容量为1费用为0的边, 表示到达Y.
  • 格子X的Xi向原方向指向的格子Y的Yj连一条容量为INF, 费用为0的边, 向另外三个方向指向的格子连一条容量为INF, 费用为1的边. 表示修改.

代码

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int INF = 1000000000;
const int maxn = 2*15*15 + 10;

struct Edge {
	int from, to, cap, flow, cost;
};

struct MCMF {
	int n, m, s, t;
	vector edges;
	vector G[maxn];
	int inq[maxn], d[maxn], p[maxn], a[maxn];

	void init(int n, int s, int t) {
		this->n = n;
		this->s = s;
		this->t = t;
	}

	void AddEdge(int from, int to, int cap, int cost) {
		edges.push_back((Edge){from, to, cap, 0, cost});
		edges.push_back((Edge){to, from, 0, 0, -cost});
		m = edges.size();
		G[from].push_back(m-2);
		G[to].push_back(m-1);
	}

	bool BellmanFord(int& cost) 
	{
		for(int i = 0; i < n; i++) d[i] = INF;
		memset(inq, 0, sizeof(inq));
		d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF;

		queue Q;
		Q.push(s);
		while(!Q.empty()) {
			int u = Q.front(); Q.pop();
			inq[u] = 0;
			for(int i = 0; i < G[u].size(); i++) {
				Edge& e = edges[G[u][i]];
				if(e.cap > e.flow && d[e.to] > d[u] + e.cost) {
					d[e.to] = d[u] + e.cost;
					p[e.to] = G[u][i];
					a[e.to] = min(a[u], e.cap - e.flow);
					if(!inq[e.to]) { Q.push(e.to); inq[e.to] = 1; }
				}
			}
		}
		if(d[t] == INF) return false;
		cost += d[t] * a[t];
		int u = t;
		while(u != s) {
			edges[p[u]].flow += a[t];
			edges[p[u]^1].flow -= a[t];
			u = edges[p[u]].from;			
		}
		return true;
	}

	int Mincost() {
		int cost = 0;
		while(BellmanFord(cost));
		return cost;
	}
}g;

int id[maxn][maxn];

int main()
{
	int n, m, s, t;
	scanf("%d %d", &n, &m);
	int c = 0;
	for(int i = 0; i < n; i++)
		for(int j = 0; j < m; j++)
			id[i][j] = ++c;
	g.init(c+c+2, s=0, t=c+c+1);
	for(int i = 0; i < n; i++) {
		char str[20];
		scanf("%s", str);
		for(int j = 0; j < m; j++) {
			g.AddEdge(s, id[i][j], 1, 0);
			g.AddEdge(id[i][j]+c, t, 1, 0);
			switch(str[j]) {
				case 'U': g.AddEdge(id[i][j], id[(i+n-1)%n][j]+c, INF, 0);
				          g.AddEdge(id[i][j], id[(i+1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+m-1)%m]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+1)%m]+c, INF, 1);
				          break;
				          
				case 'D': g.AddEdge(id[i][j], id[(i+n-1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[(i+1)%n][j]+c, INF, 0);
				          g.AddEdge(id[i][j], id[i][(j+m-1)%m]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+1)%m]+c, INF, 1);
				          break;
				
				case 'L': g.AddEdge(id[i][j], id[(i+n-1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[(i+1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+m-1)%m]+c, INF, 0);
				          g.AddEdge(id[i][j], id[i][(j+1)%m]+c, INF, 1);
				          break;
				          
				case 'R': g.AddEdge(id[i][j], id[(i+n-1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[(i+1)%n][j]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+m-1)%m]+c, INF, 1);
				          g.AddEdge(id[i][j], id[i][(j+1)%m]+c, INF, 0);
				          break;
			}
		}
	}
	printf("%d\n", g.Mincost());
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值