BZOJ 3232 圈地游戏 (分数规划 + SPFA找负/正环)

题意

BZOJ 3232

题解

对于这种 A / B A/B 的最值问题一般都是用分数规划解决。

假设 A / B A/B 最大值为 g g

A / B g A B g 0 A/B\le g\to A-Bg\le 0

那么我们二分一个 m i d mid

  • 如果 max A B m i d > 0 \max{A-B\cdot mid}>0 ,说明 m i d < g mid<g
  • 如果 max A B m i d < 0 \max{A-B\cdot mid}<0 ,说明 m i d > g mid>g
  • 如果 max A B m i d = 0 \max{A-B\cdot mid}=0 ,说明 m i d = g mid=g

那么问题转换为求 max A B m i d \max{A-B\cdot mid}

对于这道题,发现合法路径是一个环,那么我们把边的权值乘上 m i d -mid ,那么就是求是否存在边权加上围成图形内的点权为正的环。找正环就 S P F A SPFA 就行了。

考虑如何记录围成的点权和。

我们给环顶一个方向,逆时针。那么我们向上走就加上这一行左边的前缀和,向下走就减去这一行左边的前缀和。同时路径上的边权要加。那么这样形成一个环恰好就包含了中间的点权和。

CODE

#include <cstdio>
#include <queue>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
template<class T>inline void read(T &x) {
	char ch; while(!isdigit(ch=getchar()));
	for(x=ch-'0';isdigit(ch=getchar());x=x*10+ch-'0');
}
typedef long long LL;
const int MAXN = 2650;
const int MAXE = (MAXN<<1)<<1;
const int inf = 1000000000;
int n, m, id[55][55], xx[MAXN], yy[MAXN], tim[MAXN]; //tim存进队次数
double a[55][55], l[55][55][4], dis[MAXN];
//l[i][j][k]存从(i,j)往k方向走的边权
//a[i][j]存(i,j)这一行往左的和
queue<int>q;
bool inq[MAXN];
const int dx[4] = { 1, -1, 0, 0 };
const int dy[4] = { 0, 0, 1, -1 };
bool chk(double g) {
	for(int i = 1; i <= (n+1)*(m+1); ++i) dis[i] = -inf, tim[i] = 0, inq[i] = 0;
	//记得清零inq,本来spfa不用清零,但是这里有中途退出所以要清零
	while(!q.empty()) q.pop();
	q.push(id[1][1]); dis[id[1][1]] = 0;
	while(!q.empty()) {
		int U = q.front(); q.pop(); inq[U] = 0;
		int u = xx[U], v = yy[U]; double W;
		for(int k = 0, x, y, V; k < 4; ++k) {
			x = u + dx[k], y = v + dy[k];
			if(x >= 1 && y >= 1 && x <= n+1 && y <= m+1) {
				V = id[x][y]; W = -g*l[u][v][k];
				if(k == 0) W += -a[u][v-1];
				if(k == 1) W += a[u-1][v-1];
				if(dis[V] < dis[U] + W) {
					dis[V] = dis[U] + W;
					if(++tim[V] > (n+1)*(m+1)) return 1;
					if(!inq[V]) inq[V] = 1, q.push(V);
				}
			}
		}
	}
	return 0;
}
int main () {
	read(n), read(m);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			read(a[i][j]), a[i][j] += a[i][j-1];
	for(int i = 1; i <= n+1; ++i)
		for(int j = 1, x; j <= m; ++j) {
			read(x);
			l[i][j][2] = l[i][j+1][3] = x;
		}
	for(int i = 1; i <= n; ++i)
		for(int j = 1, x; j <= m+1; ++j) {
			read(x);
			l[i][j][0] = l[i+1][j][1] = x;
		}
	for(int i = 1; i <= n+1; ++i)
		for(int j = 1; j <= m+1; ++j) {
			id[i][j] = (i-1)*(m+1) + j; //编号
			xx[id[i][j]] = i; //存坐标
			yy[id[i][j]] = j;
		}
	double l = 0, r = 100.0*n*m, mid;
	while(r-l>1e-5) {
		mid = (l+r)/2;
		if(chk(mid)) l = mid;
		else r = mid;
	}
	printf("%.3f\n", l);
}
发布了367 篇原创文章 · 获赞 239 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Ike940067893/article/details/103320834