洛谷4307 BZOJ1449 JSOI2009 球队收益 费用流 凸费用拆边

17 篇文章 0 订阅
8 篇文章 0 订阅

题目链接

题意:
n n n支球队,每只球队会有一个赢一场和输一场获得的钱数,第 i i i只球队得到的钱数是 C i × x 2 + D i × y 2 , D i ≤ C i C_i\times x^2+D_i \times y^2,D_i \le C_i Ci×x2+Di×y2,DiCi,其中 x x x表示输的总场数, y y y表示赢的总场数, C C C D D D各是一个系数。已经踢完了一些比赛,还有 m m m场比赛要进行,会告诉你这 m m m场比赛的双方,让你决定剩下的所有场次的输赢关系,使得你要给所有球队的总钱数最少。 n , m &lt; = 1000 n,m&lt;=1000 n,m<=1000

题解:
一道非常好的费用流题目。

首先我们先介绍一些凸费用拆边。对于费用流里面的一些问题,边的价值与流量的关系是一个凸函数,通常是二次函数,这时候我们不能像通常那样建一个固定权值的边了,而是对于每一个可能的权值建一个边,边的流量设为1,价值设为凸函数上相邻两整数点之间差。常见的二次函数的平方部分两相邻整数之间的差是一个等差数列,也就是0到1,1到4,4到9,9到16…依次差了1,3,5,7…,前面再乘上系数即可。这样根据最小费用的原理,我们会从价值小的边依次选择每一条经过的边,这样把一条边拆成若干条边,就可跑费用流算法了。

接下来我们考虑有了刚才的拆边之后,这个题怎么做。首先,我们的一个想法是,输赢的函数不一样,我们对于一支球队的输赢拆点,因为是两个不同的函数。然后源点可以先向每支球队的输赢两个点连已经比完的那些比赛的流量,然后对于输赢两个函数凸费用拆边,连向另一排点,另一排点再连向汇点,这样是一个二分图,看起来复杂度很靠谱。我们考虑怎么处理剩下的比赛。我们知道,剩下的比赛一定会是一个赢一个输,也就是对于一场A和B之间的比赛,有A赢B输和A输B赢两种情况。一个队经过一场比赛后得到的流量应该要设为1,但是怎么强制捆绑,才能使得不会出现流完之后是两队都赢或者都输的结果呢?尝试拆点,或者做流量限制,发现并不能做这个限制。

但是我们不要急着全屏否定之前的想法。我们知道,我们没法做限制是因为我们要流的流量是整数,但是没法限制一赢一输,流量不行我们就在费用上做尝试,也就是我们改变每条边的费用的含义。我觉得这也是这个题的巧妙之处。我们还是除了源汇之外建两排点,第一排表示每一场比赛,第二排点表示每只球队,但是这次我们并不把每支球队的输赢拆成两个点了。我们先假设所有球队在剩下的场次里都输了,那么这个费用总和是可以提前算出来的。我们还是考虑对球队与汇点之间的边进行凸费用拆边,我们这次拆的边的权值是每多赢一场少输一场之后答案的变化,不难看出,这也是一个二次函数。因为只能有一个赢的,所以代表比赛的点向比赛双方代表的点连流量为1费用为0的边。这样转化后我们就可以处理双方一定是一胜一负的限制了。这样我们再用跑出来跑出答案,累加到之前全输情况下的答案,就是决定了剩下点的输赢之后的最小总花费了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,st,ed,hed[2000010],cnt,ji[5010][1010];
int aa[100010],b[5010],c[5010],d[5010],num[5010];
int v[20010],w[20010],q[5000010],t,h,inq[20010],f[20010];
long long ans;
struct node
{
    int from,to,c,cost,next;
}a[2000010];
inline void add(int from,int to,int c,int cost)
{
    a[++cnt].to=to;
    a[cnt].from=from;
    a[cnt].c=c;
    a[cnt].cost=cost;
    a[cnt].next=hed[from];
    hed[from]=cnt;
    a[++cnt].to=from;
    a[cnt].from=to;
    a[cnt].c=0;
    a[cnt].cost=-cost;
    a[cnt].next=hed[to];
    hed[to]=cnt;
} 
inline void bfs()
{
    memset(w,0,sizeof(w));
    memset(v,0x3f,sizeof(v));
    memset(inq,0,sizeof(inq));
    memset(f,0,sizeof(f));
    q[1]=st;
    h=1;
    t=1;
    w[st]=2e9;
    v[st]=0;
    while(h<=t)
    {
        int x=q[h];
        inq[x]=0;
        for(int i=hed[x];i;i=a[i].next)
        {
            int y=a[i].to;
            if(a[i].c&&v[y]>v[x]+a[i].cost)
            {
                v[y]=v[x]+a[i].cost;
                w[y]=min(w[x],a[i].c);
                f[y]=i;
                if(!inq[y])
                {
                    q[++t]=y;
                    inq[y]=1;
                }
            }
        }
        ++h;
    }
    for(int i=f[ed];i;i=f[a[i].from])
    {
        a[i].c-=w[ed];
        a[i^1].c+=w[ed];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    st=n+m+1;
    ed=st+1;
    for(int i=1;i<=n;++i)
    scanf("%d%d%d%d",&aa[i],&b[i],&c[i],&d[i]);
    cnt=1;
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ++b[x];
        ++b[y];
        ++num[x];
        ++num[y];
        add(st,i,1,0);
        add(i,x+m,1,0);
        add(i,y+m,1,0);
    }
    for(int i=1;i<=n;++i)
    ans+=(long long)c[i]*aa[i]*aa[i]+(long long)d[i]*b[i]*b[i];
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=num[i];++j)
        {
            add(i+m,ed,1,c[i]*(2*aa[i]+1)-d[i]*(2*b[i]-1));
            ++aa[i];
            --b[i];
        }
    }
    while(1)
    {
        bfs();
        if(w[ed]>0)
        ans+=(long long)w[ed]*v[ed];
        else
        break;
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值