思路1:
边权取负,转化为最小费用循环流问题。类似于最小费用最大流,只不过每次不是找s->t的最小费用增广路,而是找整个图的一个负费用增广圈。沿着负圈增广,每个节点的流量平衡不变,而整个循环流的总费用减小。
找负圈,用Bellman或SPFA,记录前驱。
注意:从spfa中出来的点不一定是负圈里的点,需要回溯去找。
// 4180 ms
#include <cstdio>
#include <queue>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 100+5;
const double eps = 1e-8;
int n;
vector<int> vec[maxn];
// 图
struct Edge{
int u,v,cap,flow;
double cost;
Edge(int a, int b, int c, int d, double e):u(a),v(b),cap(c),flow(d),cost(e){}
};
vector<Edge> edges;
vector<int> G[maxn];
void init(){
for(int i = 0; i <= n; ++i) G[i].clear();
edges.clear();
}
void addEdge(int u, int v, int cap, double cost){
edges.push_back(Edge(u,v,cap,0,cost));
edges.push_back(Edge(v,u,0,0,-cost));
int m = edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
//int cap[maxn][maxn], flow[maxn][maxn];
int pre[maxn], vis[maxn], asc[maxn];
double x[maxn], y[maxn], dis[maxn];
bool FindNegativeCircuit(int s, double& c){
memset(vis, 0, sizeof(vis));
for(int i = 0; i <= n; ++i) dis[i] = INF;
dis[s] = 0; asc[s] = INF;
queue<int> Q;
Q.push(s); Q.push(0);
int u = -1;
while(!Q.empty()){
int x = Q.front(); Q.pop();
int cnt = Q.front(); Q.pop();
if(cnt >= n+1){ u = x; break; }
vis[x] = 0;
for(int i = 0; i < G[x].size(); ++i){
Edge& e = edges[G[x][i]];
if(e.cap > e.flow&&dis[e.v] > dis[x] + e.cost){
dis[e.v] = dis[x] + e.cost;
asc[e.v] = min(asc[x], e.cap - e.flow);
pre[e.v] = G[x][i];
if(!vis[e.v]){ Q.push(e.v); Q.push(cnt+1); vis[e.v] = 1; }
}
}
}
if(u == -1) return false; // 没有负圈
// 找负圈中的点
memset(vis, 0, sizeof(vis));
while(!vis[u]){
//printf("%d ",u);
vis[u] = 1;
u = edges[pre[u]].u;
}
int k = u;
// 沿着负圈增广
do{
edges[pre[u]].flow += asc[k];
edges[pre[u]^1].flow -= asc[k];
c += edges[pre[u]].cost;
u = edges[pre[u]].u;
}while(u != k);
return true;
}
double solve(int s){
double c = 0;
while(FindNegativeCircuit(s,c)) ;
return -c;
}
int main()
{
//freopen("in.txt","r",stdin);
double a,b;
int kase = 1;
while(scanf("%d%lf%lf",&n,&a,&b) == 3&&n){
init();
for(int i = 0; i <= n; ++i) vec[i].clear();
for(int i = 0; i < n; ++i){
scanf("%lf%lf",&x[i], &y[i]);
int v;
while(scanf("%d",&v) == 1&&v){
vec[i].push_back(v-1);
}
}
for(int u = 0; u < n; ++u){
for(int i = 0; i < vec[u].size(); ++i){
int v = vec[u][i];
double c = b - a*sqrt((x[u]-x[v])*(x[u]-x[v]) + (y[u]-y[v])*(y[u]-y[v]));
addEdge(u, v, 1, c);
}
}
//for(int i = 0; i < n; ++i) for(int j = i+1; j < n; ++j)printf("%d->%d: %f\n",i+1, j+1, cost[i][j]);
int s = n;
for(int i = 0; i < n; ++i) addEdge(s, i, 1, 0);
double ans = solve(s);
printf("Case %d: ",kase++);
if(fabs(ans) <= eps) printf("0.00\n");
else printf("%.2lf\n",ans);
}
return 0;
}
思路2:
处理负权边。
用最小费用流求最大费用循环流时,解决负环的一种方法:
(1)先将所有边权取反。
(2)建边。正权值的边容量为1,费用为权值。负权值的边u->v拆成3条边,分别是S->v,v->u,u->T,容量都为1,v->u费用为负权的相反数,其他2条费用为0。这样会出现某个点有多条边连到S或T,可以互相抵消到一方为0为止,统计剩下多少条k,将其中1条的容量设为k,其他的全部删掉。如果全部抵消掉了,那就将连S和T的边全部删掉。(这个删边的方法有技巧)
(3)跑一次最小费用流得到的总费用,加上所有负权之和之后(注:此时答案已为负的),再取反即得到最大费用。
为什么这样可行呢?
(还没想清楚,待续)
// 将负权边变成正权边 , 320 ms
#include <cstdio>
#include <queue>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 100+5;
const double eps = 1e-8;
int n;
vector<int> vec[maxn];
int c1[maxn], c2[maxn]; // 出度和入度
// 图
struct Edge{
int u,v,cap,flow;
double cost;
Edge(int a, int b, int c, int d, double e):u(a),v(b),cap(c),flow(d),cost(e){}
};
vector<Edge> edges;
vector<int> G[maxn];
void init(int x){
for(int i = 0; i <= x; ++i) G[i].clear();
edges.clear();
}
void addEdge(int u, int v, int cap, double cost){
edges.push_back(Edge(u,v,cap,0,cost));
edges.push_back(Edge(v,u,0,0,-cost));
int m = edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
//int cap[maxn][maxn], flow[maxn][maxn];
int pre[maxn], inq[maxn], asc[maxn];
double x[maxn], y[maxn], dis[maxn];
bool Bellman_ford(int s, int t, double& c){
memset(inq, 0, sizeof(inq));
for(int i = 0; i < n+2; ++i) dis[i] = INF;
dis[s] = 0; asc[s] = INF;
queue<int> Q;
Q.push(s);
while(!Q.empty()){
int x = Q.front(); Q.pop();
inq[x] = 0;
for(int i = 0; i < G[x].size(); ++i){
Edge& e = edges[G[x][i]];
if(e.cap > e.flow&&dis[e.v] > dis[x] + e.cost){
dis[e.v] = dis[x] + e.cost;
asc[e.v] = min(asc[x], e.cap - e.flow);
pre[e.v] = G[x][i];
if(!inq[e.v]){ Q.push(e.v); inq[e.v] = 1; }
}
}
}
if(dis[t] == INF) return false; // 没有增广
c+= asc[t]*dis[t];
for(int u = t; u != s; u = edges[pre[u]].u){
edges[pre[u]].flow += asc[t];
edges[pre[u]^1].flow -= asc[t];
}
return true;
}
double MCMF(int s, int t){
double c = 0;
while(Bellman_ford(s,t,c)) ;
return c;
}
int main()
{
freopen("in.txt","r",stdin);
double a,b;
int kase = 1;
while(scanf("%d%lf%lf",&n,&a,&b) == 3&&n){
init(n+2);
for(int i = 0; i < n; ++i){
scanf("%lf%lf",&x[i], &y[i]);
vec[i].clear();
int v;
while(scanf("%d",&v) == 1&&v){
vec[i].push_back(v-1);
}
}
memset(c1, 0, sizeof(c1));
memset(c2, 0, sizeof(c2));
double sum = 0; // 所有负权之和
for(int u = 0; u < n; ++u){
for(int i = 0; i < vec[u].size(); ++i){
int v = vec[u][i];
double c = b - a*sqrt((x[u]-x[v])*(x[u]-x[v]) + (y[u]-y[v])*(y[u]-y[v]));
if(c >= 0)
addEdge(u, v, 1, c);
else {
addEdge(v, u, 1, -c);
++c1[v]; ++c2[u];
sum += -c;
}
}
}
int s = n, t = n+1;
for(int i = 0; i < n; ++i) {
if(c1[i] > c2[i]) addEdge(s, i, c1[i] - c2[i], 0);
else if(c2[i] > c1[i]) addEdge(i, t, c2[i] - c1[i], 0);
}
double cost = MCMF(s, t);
//printf("cost = %lf\n", cost);
double ans = sum - cost;
printf("Case %d: ",kase++);
if(fabs(ans) <= eps) printf("0.00\n");
else printf("%.2lf\n",ans);
}
return 0;
}