1. 程式人生 > >[NOI2012]美食節——費用流(帶權二分圖匹配)+動態加邊

[NOI2012]美食節——費用流(帶權二分圖匹配)+動態加邊

最大 set sin 最短路 最大流 pre 可能 題目 不同

題目描述

小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;
}

總結:

動態處理無處不在。

都借助了均攤或者總和的復雜度比較低的特點,達到節省空間和時間的目的。

刪除了許多並不會用到的節點或者是邊。

[NOI2012]美食節——費用流(帶權二分圖匹配)+動態加邊