题意
给定一个平面上的一些有向边,每条边涂色费用为 dx-y , 其中 d 为两点间的欧几里得距离(实数), x,y 为题中给定参数。环路的涂色费用为其上各边涂色费用和。找出一些环路,这些环路没有边相交,且环路涂色费用总和最大。
最大费用循环流
建一个费用流模型,在原图的基础上每条边容量为1,费用为边权,找一个流使得所有结点入流等于出流使得总费用最大,因无源点汇点且无最大流的说法,每个点流量平衡,称为最大费用循环流问题,可将所有边的费用取负,转换为最小费用循环流问题。
最小费用循环流求法
- 建图:原图中权值为负的边u->v,在模型中转换为s->v,v->u,u->t,容量为1,v->u的权值为u->v的绝对值;原图中权值为正的边u->v在模型中不变;
- 删边:若s->u的弧的数量a 大于 u->t的数量b,删除所有的u->t,保留一条s->u的弧,容量为a-b;若s->u的弧的数量 小于 u->t的数量b,删除所有的u->t,保留一条u->t的弧,容量为b-a;
- 最小费用循环流:新图中的最小费用最大流的最小费用值与原图中所有负权边的和即为最小费用循环流。
解法一些胡诹
-
考虑这个做法中的最小费用最大流的产生:因原图中的负权边的存在而产生源点与汇点,进而产生流量网络;
-
几种一般情况:ans1为一个回路的最小费用流,ans2为回路对应的负权边的权值和。若不存在负权边(正回路,无源汇)干脆不取好了(0-0=0),若是带负权边的正权回路,经处理会转换成一路或多路增广路,负权的绝对值和小于正权,会按照绝对值小的增广,然后消掉(ans1+ans2=0),若是带负权的负权回路,与上面同理,剩余的正权路径绝对值小,会对其进行增广,对结果的贡献为差值(ans1+ans2<0);有负权边组成的回路各个点的流入流出相等,也不存在源汇,对答案的贡献直接为(ans2<0)
-
对“求法”中“删边”的理解:一个点在非0的前提下连向s与连向t的相等,意味着在一个全部由负权组成的回路上或者充当“跳板”,只需对多出来的部分进行“回退”;
-
最大流的意义:跑最大流保证了对所有负权边的预先处理,使之可以“退回”,这样用所有负权边的和来“消”才有意义;
-
总的来看在最小费用最大流中的mincost对应原图的正权部分与部分负权边的和的差值对最后答案产生贡献,对应原图的负权部分全部消掉,ans1尽可能的小才能保证ans1+ans2尽可能小。
代码
- 为了保证输出是大于等于0的数,不输出-0,需要对结果加上1e-8;
#include <iostream>
#include <queue>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const double inf=0x7f7f7f7f7f,eps=1e-8;
int X,Y,n,g[105][105],x[107],y[105],cnt=1,to,in[105],ot[105],st=120,flow,inq[125],last[105],vis[125],cur[125],num;
double ans,d[125],ans1;
struct edge{
int v,next,f;
double w;
}e[500005];
inline void add(int u,int v,double w,int f)
{
e[++cnt].v=v;
e[cnt].f=f;
e[cnt].w=w;
e[cnt].next=last[u];
last[u]=cnt;
e[++cnt].v=u;
e[cnt].f=0;
e[cnt].next=last[v];
e[cnt].w=-w;
last[v]=cnt;
}
bool spfa()
{
queue<int>q;
memset(inq,0,sizeof(inq));
for(int i=1;i<=st;i++) d[i]=inf;
d[0]=0;
q.push(0);
while(!q.empty())
{
int u=q.front();q.pop();
inq[u]=0;
for(int i=last[u];i;i=e[i].next)
{
int v=e[i].v,f=e[i].f;
double w=e[i].w;
if(d[v]>d[u]+w&&f)
{
d[v]=d[u]+w;
if(!inq[v])
{
inq[v]=1;
q.push(v);
}
}
}
}
return fabs(d[st]-inf)>eps;
}
int dfs(int u,int dis)
{
vis[u]=1;
if(u==st||!dis) return dis;
for(int i=cur[u];i;i=e[i].next)
{
cur[u]=e[i].next;
int v=e[i].v,f=e[i].f;
double w=e[i].w;
if(fabs(d[v]-d[u]-w)<eps&&f&&!vis[v])
{
vis[v]=1;
int di=dfs(v,min(dis,f));
if(di>0)
{
e[i].f-=di;
e[i^1].f+=di;
return di;
}
}
}
return 0;
}
void dinic()
{
while(spfa())
{
for(int i=0;i<=st;i++)
cur[i]=last[i];
memset(vis,0,sizeof(vis));
while(int ml=dfs(0,inf))
{
ans+=d[st];
flow+=ml;
memset(vis,0,sizeof(vis));
}
}
}
void intt()
{
for(int i=1;i<=cnt;i++)
e[i].v=e[i].w=e[i].next=e[i].f=0;
memset(last,0,sizeof(last));
memset(in,0,sizeof(in));
memset(ot,0,sizeof(ot));
memset(g,0,sizeof(g));
ans=ans1=0;
cnt=1;
}
int main()
{
while(cin>>n&&n)
{
num++;
cin>>X>>Y;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x[i],&y[i]);
while(cin>>to&&to)
g[i][++g[i][0]]=to;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=g[i][0];j++)
{
int xx=x[g[i][j]],yy=y[g[i][j]];
double len=-sqrt((x[i]-xx)*(x[i]-xx)+(y[i]-yy)*(y[i]-yy))*X+Y;
if(len<0)
{
add(g[i][j],i,-len,1);
in[g[i][j]]++;
ot[i]++;
ans1+=len;
}
else
add(i,g[i][j],len,1);
}
for(int i=1;i<=n;i++)
{
if(in[i]>ot[i])
add(0,i,0,in[i]-ot[i]);
if(in[i]<ot[i])
add(i,st,0,ot[i]-in[i]);
}
dinic();
printf("Case %d: %.2lf\n",num,-ans-ans1+eps);
intt();
}
return 0;
}