bzoj3265 志愿者招募加强版 线性规划与网络流(费用流)

题目分析

Si S i 为第 i i 天可以工作的志愿者的种类集合,xj为第 j j 种志愿者招募的个数,那么我们可以得到若干形如:

jSixjai

的式子。我们添加变量 yi y i 使得原式变成:

(jSixj)yi=ai ( ∑ j ∈ S i x j ) − y i = a i

设该式为 Pi P i 。添加式子 P0:y0=0 P 0 : y 0 = 0 Pn+1:yn+1=0 P n + 1 : y n + 1 = 0 后,式子之间两两相减可得若干形如 Pi1Pi P i − 1 − P i 的式子,记 Ki=Pi1Pi K i = P i − 1 − P i 。并且将 Ki K i 种等号右边的常数全部移到左边。
可以发现,假如第 j j 种志愿者工作的某一段时间是[sj,tj],那么在 Ksj K s j 中, xj x j 取负号。在第 Ktj+1 K t j + 1 中, xj x j 取正号。所有这些式子中,变量 x x y一定拥有相等数量的取正号的式子和取负号的式子。
将一个 K K 式子看做点,一个取负号的变量或是常数看做流出,一个取正号的变量或常数看做流入,那么我们的目标是流量平衡,也就是等号右边为0。由于每种志愿者有不同的费用,所以使用费用流解决该问题。
对于一个yi取负号的式子,要有一条容量为inf费用为0的边指向 yi y i 取正号的式子。也就是每个 i i i+1之间有一条容量为inf的边。
对于常数,一种方法是若正则从源点流过来,若负则流到汇点去。当然你也可以从点 i i 到点i+1之间流一条容量为 ai − a i 费用为0的边(也就是将那条容量为 inf i n f 的边的容量减少)
对于一个 xi x i 取负号的式子( Ksj K s j ),向一个 xi x i 取正号的式子( Ktj+1 K t j + 1 ),连一条容量为inf费用为 ci c i 的边。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
typedef long long LL;
const int N=1005,M=1000005,inf=0x3f3f3f3f;
int n,m,tot=1,S,T,ans;
int a[N],h[N],ne[M],to[M],flow[M],w[M],l[N],r[N];
int dis[N],pre[N],liu[N],inq[N];
void add(int x,int y,int z,int c) {
    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z,w[tot]=c;
    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0,w[tot]=-c;
}
int bfs() {
    for(RI i=S;i<=T;++i) dis[i]=0x3f3f3f3f,pre[i]=inq[i]=0;
    queue<int> q;
    dis[S]=0,liu[S]=inf,q.push(S);
    while(!q.empty()) {
        int x=q.front();q.pop(),inq[x]=0;
        for(RI i=h[x];i;i=ne[i])
            if(flow[i]>0&&dis[x]+w[i]<dis[to[i]]) {
                dis[to[i]]=dis[x]+w[i],pre[to[i]]=i;
                liu[to[i]]=min(flow[i],liu[x]);
                if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);
            }
    }
    if(!pre[T]) return 0;
    ans+=liu[T]*dis[T];int x=T;
    while(x) {
        int kl=pre[x];
        flow[kl]-=liu[T],flow[kl^1]+=liu[T],x=to[kl^1];
    }
    return 1;
}
int main()
{
    int x,num;
    n=read(),m=read();
    S=0,T=n+1;
    add(S,1,inf,0);
    for(RI i=1;i<=n;++i) add(i,i+1,inf-read(),0);
    for(RI i=1;i<=m;++i) {
        num=read();
        for(RI j=1;j<=num;++j) l[j]=read(),r[j]=read();
        x=read();
        for(RI j=1;j<=num;++j) add(l[j],r[j]+1,inf,x);
    }
    while(bfs());
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值