1449/2895: [JSOI2009]球队收益

题目链接

题目大意: n 支球队,球队的支出和胜负场次有关,具体来说,第i支球队的赛季总支出是Cix2+Diy2,其中 x,y 分别表示这只球队本赛季的胜负场次。
现在赛季进行到了一半,每只球队分别取得了 ai 场胜利和 bi 场失利。而接下来还有 m 场比赛要进行。问联盟球队的最小总支出是多少。

题解:输赢都会带来收益……
可以假设初始每只队伍都输,算出初始收益,然后对每场比赛分配赢的队伍
这样每场只要修改赢的那个队伍的收益

从源向每场比赛连流量1费用 0 的边,从比赛向这场比赛的两支队都连一条流量1费用 0 的边

由于费用是动态变化的,考虑拆边

对于某支球队,假设后m场中其参加 x 场,初始w=ai l=bi+x ,之后每赢一场 w++,l

每只队向汇连x条边,分别代表其赢i场比赛时相对赢i-1场时收益的增量,即
(C(w+1)2+D(l1)2)(Cw2+Dl2)=2wC2lD+C+D

收益随胜利场数递增,因此一定先走赢比赛场数较少的边,可以保证正确性

答案为所有队伍最初收益+最小费用最大流费用

我的收获:动态费用拆边建图,差分费用……

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;

const int N=7505;
const int INF=1e9;

int n,m,ans; 
int st,ed;
int t,head[N];
int q[N],l,r;
int win[N],lose[N],C[N],D[N];
int d[N],pre[N],in[N];
bool vis[N];

struct edge{int fro,to,nex,c,val;}e[1000000];

void add(int i,int j,int ca,int co){e[t].fro=i,e[t].to=j,e[t].nex=head[i],e[t].c=ca,e[t].val=co;head[i]=t++;}
void insert(int i,int j,int w,int z){add(i,j,w,z),add(j,i,0,-z);}

bool spfa()
{
    for(int i=0;i<=ed;i++) vis[i]=0,d[i]=INF;
    d[st]=0;l=0,r=0,q[++r]=st;
    while(l<r)
    {
        int u=q[++l];vis[u]=false;
        for(int i=head[u];i!=-1;i=e[i].nex){
            int v=e[i].to;
            if(e[i].c&&d[v]>d[u]+e[i].val){
                d[v]=d[u]+e[i].val;
                pre[v]=i;
                if(!vis[v]){
                    vis[v]=true;
                    q[++r]=v;
                }
            }
        }
    }
    return d[ed]!=INF;
}

void mcfx()
{
    int mx=INF;
    for(int u=ed;u!=st;u=e[pre[u]].fro)
        mx=min(mx,e[pre[u]].c);
    for(int u=ed;u!=st;u=e[pre[u]].fro){
        e[pre[u]].c-=mx;e[pre[u]^1].c+=mx;
        ans+=e[pre[u]].val*mx;
    }
}

void work()
{
    while(spfa()) mcfx();
    printf("%d\n",ans);
}

void build()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=in[i];j++){
            insert(i+m,ed,1,2*C[i]*win[i]+C[i]+D[i]-2*D[i]*lose[i]);
            lose[i]--;win[i]++;
        }
}

void init()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);ed=n+m+1;
    for(int i=1;i<=n;i++) scanf("%d%d%d%d",&win[i],&lose[i],&C[i],&D[i]);
    for(int u,v,i=1;i<=m;i++){
        insert(st,i,1,0);
        scanf("%d%d",&u,&v);
        insert(i,u+m,1,0),insert(i,v+m,1,0);
        in[u]++,in[v]++;
    }
    for(int i=1;i<=n;i++) lose[i]+=in[i];
    for(int i=1;i<=n;i++) ans+=win[i]*win[i]*C[i]+lose[i]*lose[i]*D[i];
    build();
}

int main()
{
    init();
    work();
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值