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