BZOJ 3232 圈地游戏 (分数规划 + 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);
}