zoukankan      html  css  js  c++  java
  • [NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]

    题面

    传送门

    思路

    引入:网络流?

    看到这道题,第一想法是用一个dp来完成决策

    但是,显然这道题的数据并不允许我们进行dp,尤其是有10000种志愿者的情况下

    那么我们就要想别的办法来解决:

    贪心?这道题做的决策除了价值以外还有覆盖面问题,而且更致命的是每一天的人数需求还不一样

    那么题目限制如此多元化的题目,我们就一定要想到用那个传说中的万能算法——网络流

    想到网络流以后,一个直观的想法就是源点连人,人连可以被雇佣的日子,日子连汇点,然后跑最小费用最大流

    但是这样有一个问题:本题中的雇佣不是可以一天用一天不用的,而是你花了那么多钱,他就一次性帮你从Si做到Ti,不能按天购买

    而我们的这个网络流模型显然可以让流量从每个“人点”的一部分出边流出,并不正确

    “一面对多面”

    这里的这个问题,我称之为“一面对多面”,也就是一个决策会影响到多个限制条件的改变,但是网络流中的流量是一对一的,也就是一点流量不能在某一个点“分”成很多流量,所以并不能解决这种“一面对多面”的问题

    那我们就要考虑更换思路了

    我们之前的想法是以“一个被雇佣的人”为“一点流量”,但是仔细思索,发现出现上述问题的本质在于:不同的日子可能会用同一个人,而代表同一个人的流量只有一

    那我们就要想办法让这一个流量,去覆盖所有的Si到Ti的日子

    这引导我们想到如下模型:

    对于每一中志愿者(si,ti,ci),我们建一条跨过si到ti的所有点的边,费用为ci,来表示“这一个流量一直流完了这些区域”

    但是问题在于,只是这样建图的话,并没有把每天的人数限制ai放到图里,也就是这个图缺少信息

    补全信息

    此时我们需要想办法把人数限制放到图里

    我们考虑最大流算法:它会求出最大的流量

    那我们既然用一点流量表示一个人,那么为什么我们不把这个“需要用人”的限制,放到另外几条边上呢?

    我们在点(i,i+1)之间建边,设流量为-a[i],也就是负的当天需求数,费用自然是零的

    然后,令上文中的志愿者(si,ti,ci),建边(si,ti+1),费用ci,流量无限

    此时我们相当于是把第i天的决策放到了第i个点和第i+1个点之间的所有边上(就是把所有点排成一排,这两个点之间的那一条位置里的所有边,包括跨过这个区间的志愿者边)

    需要志愿者?让它们从志愿者边上流过去,同时让人数限制边满流到-a[i],这样求一个1-n+1的最大流,流量为0的最小费用就是雇佣人的最小费用了

    为了让这个限制起效,又因为网络流中流量非负,所以我们建立点SS和TT,连边(SS,1)(n+1,TT),限制为inf,费用为0

    同时,我们把之前的人数限制边的流量改成(inf-a[i]),这样最终的SS-TT最大流一定是inf,而且限制依然成立

    完成

    总结

    到这里,我们就完成了本题的建模,可以看到这个过程是复杂而十分深刻的,我们从最开始的暴力、dp,转化到网络流,又从“一面对多面”的问题中跳了出来,转化到了最后的方法,又增加了inf的流量来使整个图合法

    可以看到,这道题里主要解决的就是两个矛盾:dp时间复杂度不够,以及“一面对多面”不可行

    第一个矛盾是时间上的,我们发现无法进行优化以后,转换了算法

    第二个矛盾在于建模之上,我们采取了研究算法本质、重新构建模型的方法,舍弃了常见的建图模型而建立了这道题的独有模型

    由此可见,做题的时候思路一定要放开,要大胆;如果发现常用的“套路”不合适,就要果断放弃、选择新方法

    同时,研究算法、建立模型要从算法本质出发;一定要先想好算法中的什么东西代表题目中的哪个东西,再放到算法框架中,针对性构建模型;不能生搬硬套旧套路

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define inf 1e9
    using namespace std;
    inline int read(){
        int re=0,flag=1;char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-') flag=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
        return re*flag;
    }
    int n,m,cnt=-1,ans,first[10010],dis[10010],vis[10010];
    struct edge{
        int to,next,w,cap;
    }a[200010];
    inline void add(int u,int v,int w,int cap){
        a[++cnt]=(edge){v,first[u],w,cap};first[u]=cnt;
        a[++cnt]=(edge){u,first[v],-w,0};first[v]=cnt;
    }
    int q[100010];
    bool spfa(int s,int t){
        int head=0,tail=1,i,u,v,w;
        memset(dis,-1,sizeof(dis));memset(vis,0,sizeof(vis));
        q[0]=t;vis[t]=1;dis[t]=0;
        while(head<tail){
            u=q[head++];vis[u]=0;
            for(i=first[u];~i;i=a[i].next){
                v=a[i].to;w=a[i].w;
                if(a[i^1].cap&&((dis[v]==-1)||(dis[v]>dis[u]-w))){
                    dis[v]=dis[u]-w;
                    if(!vis[v]) q[tail++]=v,vis[v]=1;
                }
            }
        }
        return ~dis[s];
    }
    int dfs(int u,int t,int limit){
        if(u==t||!limit){vis[u]=1;return limit;}
        int i,v,f,flow=0,w;vis[u]=1;
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;w=a[i].w;
            if(!vis[v]&&(dis[v]==dis[u]-w)&&(a[i].cap)){
                if(!(f=dfs(v,t,min(limit,a[i].cap)))) continue;
                a[i].cap-=f;a[i^1].cap+=f;ans+=f*w;
                flow+=f;limit-=f;
                if(!limit) return flow;
            }
        }
        return flow;
    }
    int zkw(int s,int t){//zkw费用流
        int re=0;
        while(spfa(s,t)){
            vis[t]=1;
            while(vis[t]){
                memset(vis,0,sizeof(vis));
                re+=dfs(s,t,inf);
            }
        }
        return re;
    }
    int main(){
        int i,t1,t2,t3;memset(first,-1,sizeof(first));
        n=read();m=read();
        for(i=1;i<=n;i++) t1=read(),add(i,i+1,0,inf-t1);//构建“人数限制边”
        add(0,1,0,inf);add(n+1,n+2,0,inf);
        for(i=1;i<=m;i++){//构建“志愿者边”
            t1=read();t2=read();t3=read();
            add(t1,t2+1,t3,inf);
        }
        zkw(0,n+2);//最大流值一定是inf,费用就是答案
        cout<<ans<<endl;
    }
    
  • 相关阅读:
    深入理解Java:注解(Annotation)--注解处理器
    深入理解Java:注解(Annotation)基本概念
    深入理解Java:注解(Annotation)自定义注解入门
    SpringMVC从Controller跳转到另一个Controller
    使用 Spring 2.5 注释驱动的 IoC 功能
    SpringMVC之controller篇
    Oracle存储过程in、out、in out 模式参数
    初识Flutter
    浅谈Android 6.0之Runtime Permissions
    Java版斯诺克开源分享
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/8777535.html
Copyright © 2011-2022 走看看