【BZOJ1449】球队收益(JSOI2009)-费用流+拆边
测试地址:球队收益
做法:本题需要用到费用流+拆边。
由于每场比赛只能有一个胜者,不难想到从每场比赛向比赛双方的队伍各连一条容量为的边,并从源点向每场比赛连一条容量为的边,这样每个队伍获得的流量就是它赢的场次了。我们先假设一支队伍所涉及的所有比赛它都输了,这种情况下会获得的收益,其中为这支队伍参加的场次,就是它已经赢/输的场次。那么如果赢了场,它就会获得比原来多的收益。我们发现这个很难表现成一条边的费用,于是我们把这条边拆成条容量为的边,并使得它在选其中费用最少的条边时,费用和为上面那个式子,我们可以构造出第条边的费用应该为,即从赢场到赢场新增的收益,显然这个东西是随着的增大而增大的,所以满足上面的条件。那么我们把图建出来后,跑一遍最小费用最大流就可以求出最小的新增收益,最后加上原来我们假设已经获得的收益即可。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000000000000ll;
int n,m,S,T,first[6010]={0},tot=1,last[6010],laste[6010];
ll x[5010],y[5010],c[5010],d[5010],deg[5010]={0};
ll dis[6010],sum=0;
bool vis[6010];
queue<int> Q;
struct edge
{
int v,next;
ll f,c;
}e[30010];
void insert(int a,int b,ll f,ll c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld%lld%lld%lld",&x[i],&y[i],&c[i],&d[i]);
S=n+m+1,T=n+m+2;
for(int i=1;i<=m;i++)
{
int a,b;
insert(S,i,1,0);
scanf("%d%d",&a,&b);
deg[a]++,deg[b]++;
insert(i,m+a,1,0);
insert(i,m+b,1,0);
}
for(int i=1;i<=n;i++)
{
sum+=c[i]*x[i]*x[i]+d[i]*(y[i]+deg[i])*(y[i]+deg[i]);
for(ll j=1;j<=deg[i];j++)
insert(m+i,T,1,c[i]*(2ll*(x[i]+j)-1ll)-d[i]*(2ll*(y[i]+deg[i]-j)+1));
}
}
bool spfa()
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=T;i++)
dis[i]=inf;
dis[S]=0;
vis[S]=1;
Q.push(S);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]>dis[v]+e[i].c)
{
last[e[i].v]=v;
laste[e[i].v]=i;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[T]!=inf;
}
void mincost()
{
ll minc=0;
while(spfa())
{
int x=T;
ll maxf=inf;
while(x!=S)
{
maxf=min(maxf,e[laste[x]].f);
x=last[x];
}
x=T;
while(x!=S)
{
e[laste[x]].f-=maxf;
e[laste[x]^1].f+=maxf;
x=last[x];
}
minc+=maxf*dis[T];
}
printf("%lld",sum+minc);
}
int main()
{
init();
mincost();
return 0;
}