【BZOJ1061】【codevs1803】志愿者招募,神奇建图费用流

传送门1
传送门2
写在前面:第一次写成功的费用流是个神奇数学建模题……
思路:摘自http://www.ithao123.cn/content-4207689.html,感觉这个要比列不等式+松弛操作的说法更加明白简单

设每个时间i都需要有至少Ai个志愿者,设每种志愿者i使用了xi个,那么我们对于每个时间点都可以列出一个不等式:x1+x2+x3+…+xn>=Ai(其中如果第i类志愿者不能在该区间工作则xi固定为0)。

最后要求最小化w1*x1+w2*x2+x3*x3+…+wn*xn(其中wi是第i种志愿者的单位价格)。

这正是一个线性规划的问题。

那么我们是否可以转化为网络流来求解呢?

当然是可以哒~

现在我们将每个点的Ai取反,变成-Ai,这样他就成了一个“坑”了(相对于y=0而言),我们从源点S给最左边的时间点引流,流的大小为U(U为大整数),然后每个时间点i向时间点i+1建边(i,i+1,U-Ai,0),最后设汇点为最右端的时间点。

现在,如果所有时间点的Ai都为0,那么显然,汇点的流量恰好等于U。

问题来了,现在我们有的Ai不等于0了,那么显然源点到汇点的流量被这些“坑”所截断了,怎么解决这一个问题?

假设志愿者工作的时间为【Li,Ri】,且该种志愿者单位花费为Ci,则我们建边(Li,Ri+1,INF,Ci),表示我们雇佣了一些志愿者“填坑”来了,如果雇佣了xi个志愿者,则说明将该区间内的所有“坑”的深度填掉了xi(当然可能有的坑在“填坑”行动后高于y=0,那也无所谓了嘛,多多益善~)。

那么现在是不是看起来思路有一点清晰了?

于是志愿者的作用就是一个人可以填一个区间的“坑”(好厉害!),然后需要每种志愿者选择一些使得花费最小的情况下填掉所有的“坑”,就是这样~

最后就是让费用流帮我们选择志愿者的时候了~

于是我们按照上面的方法跑完一遍最小费用最大流,如果流量等于U,则说明满流(志愿者们成功填掉了所有的坑,同志们辛苦了~),此时的记录的cost就是最小值(cost为算法记录的最小费用)。如果流量不等于U则无解。

这时我们又收获了一种费用流的模型:初始给一道大流,然后将有至少覆盖次数限制的点(边)的权值取反变成“坑”,最后区间覆盖就等于“填坑”,只要最后的流量等于大流的流量,就有解。

注意:
1.建图不要想当然,一定要遵从流量守恒等原则,最好在纸上画画写写,并手动跑一遍
2.小心int溢出,这个好坑的
代码:

#include"bits/stdc++.h"
#define Maxn 0x7fffffff
#define LL long long
using namespace std;
int tot=1,s,t,n,m,up[20010];
bool flag[20010];
LL ans,dis[20010];
int person[1010],start[10010],end[10010],c[10010],first[20010];
struct os
{
    int fa,son,next;
    LL limit,cost;
}a[100000];
queue<int> q;
void add(LL x,LL y,LL z,LL co)
{
    a[++tot].fa=x;
    a[tot].son=y;
    a[tot].limit=z;
    a[tot].cost=co;
    a[tot].next=first[x];
    first[x]=tot;
}
bool spfa()
{
    memset(dis,127,sizeof(dis));
    memset(up,0,sizeof(up));
    dis[s]=0;
    flag[s]=1;
    q.push(s);
    LL k;
    while (!q.empty())
    {
        k=q.front();
        for (LL i=first[k];i;i=a[i].next)
        if (a[i].limit&&dis[a[i].son]>dis[k]+a[i].cost)
        {
            dis[a[i].son]=dis[k]+a[i].cost;
            up[a[i].son]=i;
            if (!flag[a[i].son]) flag[a[i].son]=1,q.push(a[i].son);
        }
        q.pop();
        flag[k]=0;
    }
    if (dis[t]<0x7fffff) return 1;
    else return 0;
}
LL flow()
{
    LL ans=0,minn=0x7fffffff;
    for (int i=up[t];i;i=up[a[i].fa])
    minn=min(minn,a[i].limit);
    if (minn==Maxn) return 0;
    for (int i=up[t];i;i=up[a[i].fa])
    ans+=minn*a[i].cost,
    a[i].limit-=minn,
    a[i^1].limit+=minn;
    return ans;
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    scanf("%d",&person[i]);
    for (int i=1;i<=m;i++)
    scanf("%d%d%d",&start[i],&end[i],&c[i]);
    s=n+2;
    t=n+1;
    add(s,1,Maxn,0);
    add(1,s,0,0);
    for (int i=1;i<=n;i++)
    add(i,i+1,Maxn-person[i],0),
    add(i+1,i,Maxn,0);
    for (int i=1;i<=m;i++)
    add(start[i],end[i]+1,Maxn,c[i]),
    add(end[i]+1,start[i],0,-c[i]);
    while (spfa()) 
    ans+=flow();
    printf("%lld",ans);
}

你可能感兴趣的:(【BZOJ1061】【codevs1803】志愿者招募,神奇建图费用流)