思路:
- 题目要求输出比样例更优的任意一个解,用最小费用流建模求出最优解会TLE。
- 某个流f是同流量中的最小费用流,等价于f的残余网络中没有负圈。<最小费用流的证明>
- 构造残余网络,从汇点开始遍历,因为源点s发出的边满流了,即没有容量为0。用spfa算法找负圈,注意跳出的点不一定在负圈上(可能是与负圈有多个边相连的点,也会多次更新)!然后更新一下负圈上的点即可。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int Max_v=1100;
int N,M;
struct Node{
int x,y,c;
}no[Max_v];
int E[Max_v][Max_v];
int d[Max_v][Max_v]; //距离矩阵
int dist[Max_v],cnt[Max_v],prv[Max_v];
bool used[Max_v];
void spfa(int t){
memset(dist,0x3f,sizeof(dist));
memset(cnt,0,sizeof(cnt));
memset(used,0,sizeof(used));
queue<int>que;
que.push(t);
dist[t]=0;used[t]=1;cnt[t]=1;
while(!que.empty()){
int u=que.front();que.pop();
used[u]=0;
for(int i=0;i<=t;i++){
if(d[u][i]!=inf&&dist[i]>dist[u]+d[u][i]){
dist[i]=dist[u]+d[u][i];
prv[i]=u;
if(!used[i]){
used[i]=1;cnt[i]++;
que.push(i);
if(cnt[i]>t){
printf("SUBOPTIMAL\n");
memset(used,0,sizeof(used));
used[i]=1;
int sta; //找到一个负圈上的点
for(int j=prv[i];!used[j];j=prv[j]){
used[j]=1;
sta=j;
}
int u=prv[sta],v=sta;
do{
if(u>N)E[v][u]--;
else E[u][v]++;
v=u;u=prv[u];
}while(v!=sta);
for(int i=1;i<=N;i++){
for(int j=N+1;j<=N+M;j++)
printf("%d%c",E[i][j],j==N+M?'\n':' ');
}
return;
}
}
}
}
}
printf("OPTIMAL\n");
}
int main()
{
scanf("%d%d",&N,&M);
for(int i=1;i<=N+M;i++){
scanf("%d%d%d",&no[i].x,&no[i].y,&no[i].c);
}
int s=0,t=N+M+1;
memset(d,0x3f,sizeof(d));
int x[Max_v],y[Max_v];
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
for(int i=1;i<=N;i++){ //构造残余网络
for(int j=N+1;j<=N+M;j++){
scanf("%d",&E[i][j]);
x[i]+=E[i][j];
y[j]+=E[i][j];
int cost=abs(no[i].x-no[j].x)+abs(no[i].y-no[j].y)+1;
d[i][j]=cost;
if(E[i][j]>0)d[j][i]=-cost;
}
}
for(int i=1;i<=N;i++){
if(x[i]<no[i].c)d[s][i]=0;
if(x[i])d[i][s]=0;
}
for(int i=N+1;i<=N+M;i++){
if(y[i]<no[i].c)d[i][t]=0;
if(y[i])d[t][i]=0;
}
spfa(t);
return 0;
}