P3980 [NOI2008] 志願者招募
不會寫線性規劃,所以我寫的最小費用最大流
神仙建模題
首先,我們建一條 \(S->1\) 的邊 (S,1,0,inf)
和一條 \(n+1->T\) 的邊 (n+1,T,0,inf)
這樣就保證了我們這張圖的最大流為 \(inf\)
然後考慮刻畫題目:
首先將每一天 \(i\) 向後一天連一條 \(flow=inf-a[i]\) , \(w=0\) 的邊
表示這個點這一天需要 \(a[i]\) 個志願者
然後我們考慮如何聘請志願者:
先說如何建邊:對於每種邊(l,r,w),建一條(l,r+1,w,inf)
這樣一來我們上面刻畫(i,i+1,0,inf-a[i]),表示第i天需要a[i]的正確性就得到了證明:
顯然,要想在這個圖中跑出最大流,那麼對於n+1時的流量就應該大於等於inf。
我們來看看我們的演算法做了什麼:首先,在第一次找增廣路時,S->T的流量應該是inf-max(a[i]);
顯然,這對於希望跑滿inf的T來說時不夠的,那麼之後我們透過購買的方式從一些點在一些區間內買到了max(a[i])或者這個點本身就還剩這些
所以我們對第一次找完增廣路的圖理解可能比較容易:
第一次跑完增廣路後,每個點的剩餘流量即為自己所需志願者數與全域性志願者需求最大數的差
那麼我們在將最大流跑到inf的這一段時間中,所做的事就是不斷的增廣S->T的路徑直到T的入流量為inf
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int M=10005;
const int inf=1e9;
int head[N],arc[N],dis[N],dl[N],a[N];
int n,m,S,T,e_cnt=1,ans;
struct Edge{
int to,nxt,w,flow;
}e[M<<2];
void add(int x,int y,int w,int flow)
{
e[++e_cnt]={y,head[x],w,flow};head[x]=e_cnt;
e[++e_cnt]={x,head[y],-w,0};head[y]=e_cnt;
}
void init()
{
for(int i=0;i<N;i++)
{
dis[i]=inf;
arc[i]=head[i];
}
}
queue<int> Q;
bool spfa(int s,int t)
{
init();
dis[s]=0;dl[s]=1;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();Q.pop();dl[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w&&e[i].flow>0)
{
dis[v]=dis[u]+w;
if(!dl[v])
{
dl[v]=1;
Q.push(v);
}
}
}
}
return dis[t]!=inf;
}
int dfs(int u,int t,int in)
{
if(u==t)return in;
int out=0;
dl[u]=1;
for(int &i=arc[u];i&∈i=e[i].nxt)
{
int v=e[i].to,w=e[i].w;
if(dis[v]==dis[u]+w&&e[i].flow>0&&!dl[v])
{
int fl=dfs(v,t,min(e[i].flow,in));
e[i].flow-=fl,e[i^1].flow+=fl;
in-=fl;out+=fl;
}
}
dl[u]=0;
return out;
}
void dinic(int s,int t)
{
while(spfa(s,t))
{
int fl=dfs(s,t,inf);
ans+=fl*dis[t];
}
printf("%d\n",ans);
}
void solve()
{
cin>>n>>m;
S=0,T=n+2;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
add(i,i+1,0,inf-a[i]);
}
add(S,1,0,inf);
add(n+1,T,0,inf);
for(int i=1,x,y,w;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&w);
add(x,y+1,w,inf);
}
dinic(S,T);
}
int main()
{
//freopen("P3980.in","r",stdin);//freopen("P3980.out","w",stdout);
solve();
return 0;
}