luogu P3980 [NOI2008] 志愿者招募

P3980 [NOI2008] 志愿者招募

题意

申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要 n 天才能完成,其中第 i 天至少需要 ai 个人。布布通过了解得知,一共有 m 类志愿者可以招募。其中第 i 类可以从第 si 天工作到第 ti 天,招募费用是每人 ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。

题解

这道题的建图好妙啊,以前没有见过,想多做几道类似的题,目前还不会上下界网络流,之后再补。
首先看完题目我们可以想到一个比较经典的想法,源点向人连边,人向天连边,天向汇点连边。
但这样有些问题,我们不是很好的处理流量与费用之间的关系。
这里有一个比较新奇的建边方式,可以规避一些问题。
1.源点向第 1 天连一条,流 0,费 0 的边。
2.第 i 天向 i+1 天连接一条流 i+1天连一条流 ai,费 0 的边。
3.对于每个人的 si 天向 ti 连一条流 0 ,费 ci 的边。
4.第 n+1 条边连向汇点一条流 0,费 0 的边 。

这为什么是对的呢, 我们来仔细分析一下。
首先我们知道费用流会优先走费用小的边,这道题的连边方式会让所有负边都流空,所以我们假设走完所有天之间的边之后的总流为 ai
但是显然我们这张图的最大流为 0 ,所以我们接下来我们会通过走第 3 类边将总流重新填回 0
而我们实际上是在天的边和人的边做选择,这也是 siti+1 连的这条边和之间的边作选择,同时也保证了一个人直接做了一个区间。
最后由于最大流不能是负数,所以我们给所有的边的流量加上 INF由于我们是对差跑网络流,所以是不影响的。

code

#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define rg register
#define pc putchar
#define gc getchar
#define pf printf
#define space pc(' ')
#define enter pc('\n')
#define me(x,y) memset(x,y,sizeof(x))
#define pb push_back
#define FOR(i,k,t,p) for(rg int i(k) ; i <= t ; i += p)
#define ROF(i,k,t,p) for(rg int i(k) ; i >= t ; i -= p)
using namespace std ;
const int N = 1e6+5 ;
const int INF = 1e9+7 ;
int n,m,s,t,ans1,ans2,clow,cnt = 1 ;
int dis[N],vis[N],a[N] ;
struct Edge{int v,c,w ;}E[N] ;
vector<int>e[N] ;
inline void read(){}
template<typename T,typename ...T_>
inline void read(T &x,T_&...p)
{
    x = 0 ;rg int f(0) ; rg char c(gc()) ;
    while(!isdigit(c)) f |= (c=='-'),c = gc() ;
    while(isdigit(c)) x = (x<<1)+(x<<3)+(c^48),c = gc() ;
    x = (f?-x:x) ;
    read(p...);
}
int stk[30],tp ;
inline void print(){}
template<typename T,typename ...T_>
inline void print(T x,T_...p)
{
    if(x < 0) pc('-'),x = -x ;
    do stk[++tp] = x%10,x /= 10 ; while(x) ;
    while(tp) pc(stk[tp--]^48) ; space ;
    print(p...) ;
}
inline void Add(int u,int v,int c,int w)
{
    E[++cnt] = {v,c,w},e[u].pb(cnt) ;
    E[++cnt] = {u,-c,0},e[v].pb(cnt) ;
}
inline int Bfs()
{
    queue<int>q ; q.push(s),me(dis,0x3f),dis[s] = 0 ;
    while(!q.empty())
    {
        int x = q.front() ; q.pop() ;
        for(auto id:e[x])
        {
            auto [v,c,w] = E[id] ;
            if(dis[v] <= dis[x]+c || !w) continue ;
            dis[v] = dis[x]+c,q.push(v) ;
        }
    }
    return dis[t]!=dis[0] ;
}
int Dfs(int x,int flow)
{
    if(x == t) return flow ;
    int us = 0 ; vis[x] = 1 ;
    for(auto id:e[x]) 
    {
        auto &[v,c,w] = E[id] ;
        if(dis[x]+c != dis[v] || !w || vis[v]) continue ;
        int low = Dfs(v,min(flow-us,w)) ;
        if(low) w -= low,E[id^1].w += low,us += low ; 
        if(us == flow) break ;
    }
    if(!us) dis[x] = dis[0] ;
    return us ;
}
signed main()
{
//	freopen(".in","r",stdin) ;
//	freopen(".out","w",stdout) ;
    read(n,m) ;
    s = n+2,t = s+1 ; //print(n,m),enter ;
    FOR(i,1,n,1) read(a[i]),Add(i,i+1,0,INF-a[i]) ;
    FOR(i,1,m,1)
    {

        int si,ti ; read(si,ti,a[i]) ;
        Add(si,ti+1,a[i],INF) ;
    }
    Add(s,1,0,INF),Add(n+1,t,0,INF) ;
    while(Bfs())
    {
        me(vis,0) ;
        clow = Dfs(s,INF*2),ans1 += clow,ans2 += clow*dis[t] ;
    } 
    print(ans2) ;
    return 0 ;
}                                                                                                 

...

总结一下。
一个人可以做多个地方连续并且只有一次代价的时候,考虑将区间的贡献捆绑在一起,运用网络流的特性将其看作差,走一条边会将其跨过的边全部都作贡献。
这道题最重要的两个点就是将贡献捆绑和对差操作。

本文作者:谭皓猿

本文链接:https://www.cnblogs.com/snow-panther/p/17498635.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   谭皓猿  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起