1. 程式人生 > >【NOI2008】BZOJ1061志願者招募

【NOI2008】BZOJ1061志願者招募

1061: [Noi2008]志願者招募
Time Limit: 20 Sec Memory Limit: 162 MB
Submit: 3028 Solved: 1872
Description
申奧成功後,布布經過不懈努力,終於成為奧組委下屬公司人力資源部門的主管。布布剛上任就遇到了一個難題:為即將啟動的奧運新專案招募一批短期志願者。經過估算,這個專案需要N 天才能完成,其中第i 天至少需要Ai 個人。 布布通過了解得知,一共有M 類志願者可以招募。其中第i 類可以從第Si 天工作到第Ti 天,招募費用是每人Ci 元。新官上任三把火,為了出色地完成自己的工作,布布希望用盡量少的費用招募足夠的志願者,但這並不是他的特長!於是布布找到了你,希望你幫他設計一種最優的招募方案。
Input
第一行包含兩個整數N, M,表示完成專案的天數和可以招募的志願者的種類。 接下來的一行中包含N 個非負整數,表示每天至少需要的志願者人數。 接下來的M 行中每行包含三個整數Si, Ti, Ci,含義如上文所述。為了方便起見,我們可以認為每類志願者的數量都是無限多的。
Output
僅包含一個整數,表示你所設計的最優方案的總費用。
Sample Input
3 3
2 3 4
1 2 2
2 3 5
3 3 2
Sample Output
14
HINT
招募第一類志願者3名,第三類志願者4名 30%的資料中,1 ≤ N, M ≤ 10,1 ≤ Ai ≤ 10; 100%的資料中,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000,題目中其他所涉及的資料均 不超過2^31-1。
這道題正確的解法是構造網路,求網路最小費用最大流,但是模型隱藏得較深,不易想到。構造網路是該題的關鍵,以下面一個例子說明構圖的方法和解釋。
例如一共需要4天,四天需要的人數依次是4,2,5,3。有5類志願者,如下表所示:
種類 1 2 3 4 5
時間 1-2 1-1 2-3 3-3 3-4
費用 3 4 3 5 6
設僱傭第i類志願者的人數為X[i],每個志願者的費用為V[i],第j天僱傭的人數為P[j],則每天的僱傭人數應滿足一個不等式,如上表所述,可以列出
P[1] = X[1] + X[2] >= 4
P[2] = X[1] + X[3] >= 2
P[3] = X[3] + X[4] +X[5] >= 5
P[4] = X[5] >= 3
對於第i個不等式,新增輔助變數Y[i] (Y[i]>=0) ,可以使其變為等式
P[1] = X[1] + X[2] - Y[1] = 4
P[2] = X[1] + X[3] - Y[2] = 2
P[3] = X[3] + X[4] +X[5] - Y[3] = 5
P[4] = X[5] - Y[4] = 3
在上述四個等式上下新增P[0]=0,P[5]=0,每次用下邊的式子減去上邊的式子,得出
① P[1] - P[0] = X[1] + X[2] - Y[1] = 4
② P[2] - P[1] = X[3] - X[2] -Y[2] +Y[1] = -2
③ P[3] - P[2] = X[4] + X[5] - X[1] - Y[3] + Y[2] =3
④ P[4] - P[3] = - X[3] - X[4] + Y[3] - Y[4] = -2
⑤ P[5] - P[4] = - X[5] + Y[4] = -3
觀察發現,每個變數都在兩個式子中出現了,而且一次為正,一次為負。所有等式右邊和為0。接下來,根據上面五個等式構圖。
每個等式為圖中一個頂點,新增源點S和匯點T。
如果一個等式右邊為非負整數c,從源點S向該等式對應的頂點連線一條容量為c,權值為0的有向邊;如果一個等式右邊為負整數c,從該等式對應的頂點向匯點T連線一條容量為c,權值為0的有向邊。
如果一個變數X[i]在第j個等式中出現為X[i],在第k個等式中出現為-X[i],從頂點j向頂點k連線一條容量為∞,權值為V[i]的有向邊。
如果一個變數Y[i]在第j個等式中出現為Y[i],在第k個等式中出現為-Y[i],從頂點j向頂點k連線一條容量為∞,權值為0的有向邊。
構圖以後,求從源點S到匯點T的最小費用最大流,費用值就是結果。
根據上面的例子可以構造出如下網路,紅色的邊為每個變數X代表的邊,藍色的邊為每個變數Y代表的邊,邊的容量和權值標已經標出(藍色沒有標記,因為都是容量∞,權值0)。
這裡寫圖片描述


在這個圖中求最小費用最大流,流量網路如下圖,每個紅色邊的流量就是對應的變數X的值。
這裡寫圖片描述
所以,答案為43+23+3*6=36。
上面的方法很神奇得求出了結果,思考為什麼這樣構圖。我們將最後的五個等式進一步變形,得出以下結果
① - X[1] - X[2] + Y[1] + 4 = 0
② - X[3] + X[2] + Y[2] - Y[1] - 2 = 0
③ - X[4] - X[5] + X[1] + Y[3] - Y[2] + 3 = 0
④ X[3] + X[4] - Y[3] + Y[4] - 2 = 0
⑤ X[5] - Y[4] - 3 = 0
可以發現,每個等式左邊都是幾個變數和一個常數相加減,右邊都為0,恰好就像網路流中除了源點和匯點的頂點都滿足流量平衡。每個正的變數相當於流入該頂點的流量,負的變數相當於流出該頂點的流量,而正常數可以看作來自附加源點的流量,負的常數是流向附加匯點的流量。因此可以據此構造網路,求出從附加源到附加匯的網路最大流,即可滿足所有等式。而我們還要求noi_employee_3最小,所以要在X變數相對應的邊上加上權值,然後求最小費用最大流。
由於建圖太神,只能%rush出正解的神犇。。
當時知道費用流也不會建圖,等學完線性規劃update此篇博文。。
附上本蒟蒻的程式碼:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define T 1002
#define inf 0x7fffffff
int n,m,cnt=1,head,tail,dis[10001],h[10001],q[10001],ans=0,sum;
bool mark[10001],vis[10001];
struct kx
{
    int to,next,v,c;
}edge[50001];

int read()
{
    int w=0,s=1; char ch=getchar();
    while
(ch<'0' || ch>'9') { if (ch=='-') s=-1; ch=getchar(); } while (ch>='0' && ch<='9') w=w*10+ch-'0',ch=getchar(); return w*s; } void add(int u,int v,int w,int cost) { cnt++,edge[cnt].next=h[u],h[u]=cnt,edge[cnt].to=v,edge[cnt].v=w,edge[cnt].c=cost; cnt++,edge[cnt].next=h[v],h[v]=cnt,edge[cnt].to=u,edge[cnt].v=0,edge[cnt].c=-cost; } bool spfa() { memset(vis,false,sizeof(vis)); for (int i=0;i<=T;i++) dis[i]=inf; head=0; tail=1; q[0]=T; vis[T]=true; dis[T]=0; while (head<tail) { int now=q[head]; head++; vis[now]=false; for (int i=h[now];i;i=edge[i].next) if (edge[i^1].v && dis[now]-edge[i].c<dis[edge[i].to]) { dis[edge[i].to]=dis[now]-edge[i].c; if (!vis[edge[i].to]) { vis[edge[i].to]=true; q[tail++]=edge[i].to; } } } return dis[0]!=inf; } int dfs(int x,int f) { int w,used=0; mark[x]=true; if (x==T) return f; for (int i=h[x];i;i=edge[i].next) if (dis[edge[i].to]==dis[x]-edge[i].c && edge[i].v && !mark[edge[i].to]) { w=f-used; w=dfs(edge[i].to,min(w,edge[i].v)); ans+=w*edge[i].c; edge[i].v-=w; edge[i^1].v+=w; used+=w; if (used==f) return f; } return used; } int main() { int i,l=0,r,x,y,c; n=read(),m=read(); for (i=1;i<=n;i++) { r=read(); x=r-l; if (x>0) add(0,i,x,0); else add(i,T,-x,0); add(i+1,i,inf,0); l=r; } add(n+1,T,l,0); for (i=1;i<=m;i++) { x=read(),y=read(),c=read(); add(x,y+1,inf,c); } while (spfa()) { mark[T]=true; while (mark[T]) { memset(mark,false,sizeof(mark)); sum+=dfs(0,0x7fffffff); } } printf("%d",ans); return 0; }