鸣谢: http://www.cppblog.com/y346491470/articles/152317.html
【题意】:一个城市有n座建筑物,每个建筑物里面有一些人,为了在战争爆发时这些人都可以避难,城市里面建了m座避难所。每座避难所只能容纳有限人数。给出每个建筑物和避难所的坐标(题目要求距离为曼哈顿距离+1)。现在给你一种避难方案,问这种方案是否为最优方案,如果不是,请输出一种比当前更优的方案(不一定最优)。
【题解】:好明显的费用流(距离看成费用),如果此题建费用流模型找最小费用流必定超时,而且题目不需要我们找到最优方案。
定理:一个费用流是最小费用流的充要条件是这个费用流的残量网络没有负费用环。
由这个定理,我们只需判断当前方案是否有负费用圈,没有即意味着当前方案为最优方案。
如果有的话,此时只需沿着负费用圈把各边流量增加1,增流之后残量网络对应的方案肯定是一个更优方案(很容易证明)。
SPFA找负环: 某个点进入队列大于等于nodes次证明存在负环。(nodes一般情况是顶点个数,这道题给图很特殊,取max{n,m}就可以)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = ;
const int maxe = ;
const int maxdist = <<;
const int maxf = <<;
struct edge{
int u,v,c,cost;
edge(int e1=,int e2=,int e3=,int e4=){ u=e1; v=e2; c=e3; cost=e4; }
}e[maxe];
int cnt;
int head[maxn];
int next[maxe];
void add(int u,int v,int c,int w,int f){
e[cnt] = edge(u,v,c-f,w);
next[cnt]=head[u], head[u] = cnt++;
e[cnt] = edge(v,u,f,-w);
next[cnt]=head[v], head[v] = cnt++;
}
int source,sink,nodes;
int spfa(){
queue<int> que;
que.push(sink);
int dist[maxn],inque[maxn],visits[maxn],pre[maxn];
for(int i=;i<=nodes;i++)
dist[i]=maxdist,inque[i]=,visits[i]=,pre[i]=-;
int u,v,neg_loop=;
dist[sink]=;
while(!que.empty()){
u=que.front(); que.pop(); inque[u]=;
visits[u]++;
if(visits[u] > nodes) { //产生了负环
neg_loop=; break;
}
//printf("u = %d, visits[u] = %d\n",u,visits[u]);
for(int i=head[u];i>=;i=next[i])
if(e[i].c && dist[e[i].v] > dist[u]+e[i].cost){
v=e[i].v;
dist[v] = dist[u]+e[i].cost;
if(!inque[v])
que.push(v), inque[v]=;
pre[v]=i;
} }
int visited[maxn]={},i;
if(neg_loop){
int counter=nodes; //负环长肯定不超过顶点数,而抛出负环的点不一定在负环中
while(counter--)
u=e[pre[u]].u;
while(!visited[u]){
i=pre[u],visited[u]++,e[i].c--,e[i^].c++;
//printf("u = %d, i = %d, %d --> %d\n",u,i,e[i].u,e[i].v);
u=e[i].u;
}
}
return neg_loop;
}
void initial(){
cnt = ;
memset(head,-,sizeof(head));
//initial source,sink and nodes;
}
struct pos{
int x,y;
}building[],shelter[];
int getdist(pos &s,pos &t){
int ret=s.x-t.x;
if(ret < ) ret=-ret;
if(s.y > t.y) ret+=s.y-t.y;
else ret+=t.y-s.y;
ret++;
return ret;
}
int bi[],si[],dist[][],plan[][];
int main()
{
int n,m;
while(scanf("%d%d",&n,&m) != EOF){
for(int i=;i<=n;i++)
scanf("%d%d%d",&building[i].x,&building[i].y,&bi[i]);
for(int i=;i<=m;i++)
scanf("%d%d%d",&shelter[i].x,&shelter[i].y,&si[i]);
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
dist[i][j]=getdist(building[i],shelter[j]);
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
scanf("%d",&plan[i][j]);
int flag=;
//****************************************//
for(int i=;i<=n;i++) plan[i][]=;
for(int j=;j<=m;j++) plan[][j]=;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
plan[i][]+=plan[i][j],plan[][j]+=plan[i][j];
for(int i=;i<=n;i++)
if(plan[i][] != bi[i])
flag=;
for(int j=;j<=m;j++)
if(plan[][j] > si[j])
flag=;
//****************************************//一些额外的判断
initial();
source=,sink=n+m+,nodes=sink+;
for(int i=;i<=n;i++) add(source,i,bi[i],,bi[i]);
for(int j=;j<=m;j++) add(n+j,sink,si[j],,plan[][j]);
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
add(i,j+n,bi[i],dist[i][j],plan[i][j]);
if(!flag)
flag = spfa();
if(!flag) printf("OPTIMAL\n");
else {
printf("SUBOPTIMAL\n");
memset(plan,,sizeof(plan));
for(int u=,v;u<=n;u++)
for(int i=head[u];i>=;i=next[i]){
v=e[i].v;
if(v > n) v -= n;
plan[u][v] += e[i^].c;
}
for(int i=;i<=n;i++){
for(int j=;j<m;j++) printf("%d ",plan[i][j]);
printf("%d\n",plan[i][m]);
}
}
}
return ;
}