《洛谷P4307 [JSOI2009]球队收益 / 球队预算》

第一眼就觉得是费用流,但是建图确实很麻烦,逐渐递增这个费用到时想到了

solution:

首先,对于每一个事件,我们从s向这个事件连一条容量为1的边,然后对于这个事件,向两个点连一条容量为1的边。

这样满足了事件的关系。可以发现,我们的容量还是由s->事件的1来控制,只是有两个点的选择,即哪个点胜利。

第二,对于每个点,我们让它向t连cnt(在事件中出现的次数)条边,每次都去增长这个费用。

所以我们应该从最小开始,即我们一开始假定这个点在出现的事件中都输。

假定当前赢了a局,输了b局

那么由c*(a+1)^2+d*(b-1)^2*d - (a^2*c+b^2*d)  = (2*a+1)*c - (2*b-1)*d)

可以发现,每多赢一次,费用都是以当前的a,b为基准来增加上面这么多费用。

所以我们每次连边,都连容量为1,费用为较上一次增长后的费用

那么,为什么不能连容量1,2,3,4,然后费用较原始代价增长的边呢。

因为这样可能经过同时1,2的边,然后代价对于第一次的计算了两次,会出现不合法的更优情况,所以不行。

// Author: levil
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double ld;
typedef pair<int,int> pii;
const int N = 1e4;
const int M = 1e4;
const LL Mod = 998244353;
#define rg register
#define pi acos(-1)
#define INF 1e9
#define INM INT_MIN
#define dbg(ax) cout << "now this num is " << ax << endl;
namespace FASTIO{
    inline int read(){  
        int x = 0,f = 1;char c = getchar();
        while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
        while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();}
        return x*f;
    }
    void print(int x){
        if(x < 0){x = -x;putchar('-');}
        if(x > 9) print(x/10);
        putchar(x%10+'0');
    }
}
using namespace FASTIO;
void FRE(){
    freopen("data1.in","r",stdin);
    freopen("date1.out","w",stdout);
}

int n,m,s,t,cnt = -1,maxflow = 0,mincost = 0;
int head[N],pre[N],cal[N],dis[N],vis[N];//pre记录前驱,cal记录最短增广路上的最小流量,dis记录最短路
struct Node{int to,dis,flow,next;}e[M<<1];
inline void add(int u,int v,int w,int flow)//费用才是距离,流量是容量
{
    e[++cnt].to = v,e[cnt].dis = w,e[cnt].flow = flow,e[cnt].next = head[u],head[u] = cnt;
    e[++cnt].to = u,e[cnt].dis = -w,e[cnt].flow = 0,e[cnt].next = head[v],head[v] = cnt;
}
bool spfa()
{
    memset(vis,0,sizeof(vis));
    for(int i = 0;i <= t;++i) dis[i] = INF;
    queue<int> Q;
    Q.push(s);
    dis[s] = 0,vis[s] = 1,cal[s] = INF;
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        vis[u] = 0;//spfa标准操作,清除标记
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int v = e[i].to,d = e[i].dis,flow = e[i].flow;
            if(flow <= 0) continue;//没有剩余流量可流
            if(dis[v] > dis[u]+d)
            {
                dis[v] = dis[u]+d;
                cal[v] = min(cal[u],flow);//更新增广路上的最小流量
                pre[v] = i;//前驱记录i,因为是链式前向星
                if(!vis[v]) vis[v] = 1,Q.push(v);
            }
        }
    }
    if(dis[t] == INF) return false;
    return true;
}
void MCMF()
{
    while(spfa())
    {
        int x = t;
        maxflow += cal[t];
        mincost += dis[t]*cal[t];
        while(x!=s)
        {
            int i = pre[x];
            e[i].flow -= cal[t];
            e[i^1].flow += cal[t];
            x = e[i^1].to;
        }
    }
}
int A[N],B[N],C[N],D[N],sum[N];
int main()
{
    memset(head,-1,sizeof(head));
    n = read(),m = read();
    for(rg int i = 1;i <= n;++i) A[i] = read(),B[i] = read(),C[i] = read(),D[i] = read();
    s = 0,t = n+m+1;//t放在n,m输入下面。。
    for(rg int i = 1;i <= m;++i)
    {
        int x,y;x = read(),y = read();
        B[x]++,B[y]++;
        sum[x]++,sum[y]++;
        add(s,i,0,1);
        add(i,x+m,0,1);
        add(i,y+m,0,1);
    }
    LL ans = 0;
    for(rg int i = 1;i <= n;++i) ans += C[i]*A[i]*A[i]+D[i]*B[i]*B[i];
    for(rg int i = 1;i <= n;++i)
    {
        for(rg int j = 1;j <= sum[i];++j) 
        {
            add(i+m,t,(2*A[i]+1)*C[i]-(2*B[i]-1)*D[i],1);
            A[i]++,B[i]--;
        }
    }
    MCMF();
    printf("%lld\n",ans+mincost);
    //system("pause");     
    return 0;
}
View Code

 

posted @ 2020-08-16 21:30  levill  阅读(122)  评论(0编辑  收藏  举报