• [NOI2012]美食节——费用流(带权二分图匹配)+动态加边


    题目描述

    小M发现,美食节共有n种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有m个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。

    此外,小M还发现了另一件有意思的事情: 虽然这m个厨师都会制作全部的n种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用1, 2, ..., n依次编号,厨师用1, 2, ..., m依次编号,将第j个厨师制作第i种菜品的时间记为 ti,j 。

    小M认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第k道菜,则他的等待时间就是这个厨师制作前k道菜的时间之和。而总等待时间为所有同学的等待时间之和。

    现在,小M找到了所有同学的点菜信息: 有 pi 个同学点了第i种菜品(i=1, 2, ..., n)。他想知道的是最小的总等待时间是多少。

    [数据范围]

    10 n = 40 m = 100 p = 800

    其中,$p=sum_i^n p[i] $

    题解

    前置:[SCOI2007]修车

    修车这个题目,可以把工人拆成n个阶段,m*n个点。

    工人j的阶段i的意思是,正在维修,所有排在j这一队的,加上这个汽车后面还有i个汽车的汽车。

    即,如果(j,1)表示维修这一队的最后一辆汽车。

    汽车放在左部点,工人m*n个点放在右部点。

    S向car连接流1费0,工人向T连接流1费0

    作用:规定最大流为n。每个工人同一个阶段只能维修一辆。

    第i个车向阶段为k的j个工人连接:流1费k*(tr[i][j])

    作用:规定这个汽车只能被这个阶段的工人修一次。如果把这个汽车放在后面还有k个情况下修,那么总的等待时间会多出k*tr[i][j]

    那么现在,每一个增广路,都代表一个汽车选择了某个工人的某个位置。

    并且不会选多,不会重选,不会选少。

    直接费用流即可。

    但是这个“美食节”就比较麻烦了。

    (省选之于国赛。。。)

    左部点,总的菜不止n了,但是可以左边n个菜种,S到i的容量变成p[i]即可。

    如果还像上面一样暴力建边的话,那么,右部点一共有sum*m个。

    然后暴力建立完全二分图

    那么,边数会比6e6还多。

    亲测会TLE成60分。

    怎么优化?

    左部点不能少了。为了保证合法,右部点也没法少了。

    其实,真正卡SPFA一定是因为边数太多了。

    我们真的用得了这么多边么?

    显然大部分都是没用的。我们对着上限开了sum*m个点,真正匹配上的也就sum个点罢了。

    对于这种问题我们处理地多了。

    n个集合,数字一共m个。开n个vector,即动态数组。

    需要n棵线段树,一共修改n次。开n棵动态开点线段树。

    有什么共同之处??

    都叫“动态”

    好,那我们就动态加边!!

    一共其实只会跑sum次SPFA

    而求的是最小费用最大流,也就是求最短路。

    所以,第一次一定是某个厨师在1阶段做菜。然后,这个如果需要的话,这个厨师又会在2阶段做菜。

    所以,每个厨师很高的阶段,会凭空浪费时间,而且dis太大,根本用不上。

    而每次E-K是找到一条增广路增广。

    所以,我们开始只要把每个菜种i向阶段1的厨师连边,阶段1的厨师向T连边即可。

    然后,SPFA之后的upda,当处理到一个右部点(厨师j和阶段k)时候,

    把每个菜种i向这个厨师k+1阶段连一条边。

    再把厨师j的k+1阶段向T连接一条边。

    (注意一下向T连边,不能单纯只判断是一个右部点,因为网络流是可能回流反悔的。

    所以,一条增广路可能多次经过一个右部点。

    而第一次经过的右部点才是这条增广路的决策。

    立一个flag即可。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=80100;
    const int inf=0x3f3f3f3f;
    int n,m;
    int p[44],sum;
    struct node{
        int nxt,to,w,c;
    }e[2*(224000+40+5)];
    int hd[N],cnt=1;
    int tr[44][105];
    void add(int x,int y,int w,int c){
        e[++cnt].nxt=hd[x];
        e[cnt].to=y;e[cnt].w=w,e[cnt].c=c;
        hd[x]=cnt;
        
        e[++cnt].nxt=hd[y];
        e[cnt].to=x;e[cnt].w=0;e[cnt].c=-c;
        hd[y]=cnt;
    }
    int dis[N],pre[N],incf[N];
    bool vis[N];
    int s,t;
    queue<int>q;
    bool spfa(){
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        while(!q.empty())q.pop();
        dis[s]=0;
        pre[s]=0;incf[s]=inf;
        q.push(s);
        while(!q.empty()){
            int x=q.front();q.pop();
            vis[x]=0;
            for(int i=hd[x];i;i=e[i].nxt){
                if(!e[i].w) continue;
                int y=e[i].to;
                if(dis[y]>dis[x]+e[i].c){
                    dis[y]=dis[x]+e[i].c;
                    pre[y]=i;
                    incf[y]=min(incf[x],e[i].w);
                    if(!vis[y]){
                        vis[y]=1;
                        q.push(y);
                    }
                }
            }
        }
        if(dis[t]==inf) return false;
        return true;
    }
    int ans;
    void upda(){
        int x=t;
        bool fl=false;
        while(x!=s){
            //cout<<" xx "<<x<<" dis "<<dis[x]<<endl;
            if(!fl&&x>=1+n+1&&x<=1+n+sum*m){
                int k=(x-n-1+m-1)/m;
                
                int num=(x-n-1-1)%m+1;
                //cout<<" kkk "<<k<<" "<<num<<endl;
                if(k<sum){
                    add(x+m,t,1,0);
                    //add(x+m+m,t,1,0);
                    for(int i=1;i<=n;i++){
                        add(i+1,x+m,1,(k+1)*tr[i][num]);
                    }
                }
                fl=true;
            }
            e[pre[x]].w-=incf[t];
            e[pre[x]^1].w+=incf[t];
            x=e[pre[x]^1].to;
            
        }
        ans+=incf[t]*dis[t];
        //cout<<" ----------------------after after "<<ans<<endl;
    }
    int getfood(int x){
        return x+1;
    }
    int getchef(int p,int x){
        return 1+n+(p-1)*m+x;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&p[i]);sum+=p[i];
        }
        s=1,t=1+n+sum*m+1;
        int tim;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&tim);
                tr[i][j]=tim;
                add(getfood(i),getchef(1,j),1,1*tim);
            }
        }
        for(int i=1;i<=n;i++){
            add(s,getfood(i),p[i],0);
        }
        for(int i=1;i<=m;i++){
            //for(int j=1;j<=sum;j++){
              add(getchef(1,i),t,1,0);
            //}
        }
        while(spfa())upda();
        printf("%d",ans);
        return 0;
    }

    总结:

    动态处理无处不在。

    都借助了均摊或者总和的复杂度比较低的特点,达到节省空间和时间的目的。

    删除了许多并不会用到的节点或者是边。

  • 相关阅读:
    2、Qt Project之鼠标事件监控
    1、Qt Project之基本文件打开与保存
    Qt界面设计基础
    基于Keil软件的MCU环境搭建
    一次性将word中的数字和字母全部改为“Times New Roman”字体
    PAT 1004 Counting Leaves
    PAT 1003 Emergency
    DevC++ 控制台项目初始代码修改方法
    Win7在命令提示符(cmd.exe)中如何进行复制、粘贴工作
    VMware虚拟机如何在后台运行,后台运行怎么设置其在电脑右下角显示托盘图标
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9715428.html
Copyright © 2020-2023  润新知