题目链接:https://www.luogu.org/problemnew/show/P3980
这题大多数的构图方式其实是对下图的一种变形↓
此图中将每一天拆为两个点,每天的上午向下午连一条权为INF-a[i]的边,每天下午向第二天上午连一条权为INF的边,对于志愿者,从开始工作的上午向结束工作的下午连一条权INF费用为c[i]的边,跑最小费用最大流。
变形的核心思想是缩点,将INF边连接的两个点缩为一个,如下图↓
两个图实际上是等效的。
下面给出方案一(原二分图)代码↓
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=2e9;
int n,m,x,y,z,np=1,s,t,mct;
int h[2005],ln[2005],q[55505];
bool vis[2005];
struct rpg{
int li,nx,ln,ct;
}a[55505];
void add(int ls,int nx,int ln,int ct){
a[++np]=(rpg){h[ls],nx,ln,ct};
h[ls]=np;
a[++np]=(rpg){h[nx],ls,0,-ct};
h[nx]=np;
}
bool spfa(){
memset(vis,0,sizeof(vis));
for(int i=0;i<=t;++i) ln[i]=INF;
int hd=1,tl=1;
q[hd]=t;
ln[t]=0;
vis[t]=1;
while(hd<=tl){
int nw=q[hd++];
vis[nw]=0;
for(int i=h[nw];i;i=a[i].li){
if(a[i^1].ln&&ln[a[i].nx]>ln[nw]-a[i].ct){
ln[a[i].nx]=ln[nw]-a[i].ct;
if(!vis[a[i].nx]){
vis[a[i].nx]=1;
q[++tl]=a[i].nx;
}
}
}
}return ln[s]<INF;
}
int dfs(int u,int maxn){
if(u==t||!maxn) return maxn;
vis[u]=1;
int sum=0;
for(int i=h[u];i;i=a[i].li){
if(a[i].ln&&!vis[a[i].nx]&&ln[a[i].nx]==ln[u]-a[i].ct){
int f=dfs(a[i].nx,min(maxn,a[i].ln));
if(f){
maxn-=f;
sum+=f;
a[i].ln-=f;
a[i^1].ln+=f;
if(!maxn) break;
}
}
}return sum;
}
void dnc(){
while(spfa()){
vis[t]=1;
while(vis[t]){
memset(vis,0,sizeof(vis));
int d=dfs(s,INF);
mct+=d*ln[s];
}
}
}
int main(){
scanf("%d%d",&n,&m);t=(n<<1)+1;
for(int i=1;i<=n;++i){
scanf("%d",&x);
add(i,i+n,INF-x,0);
if(i<n) add(i+n,i+1,INF,0);
}for(int i=1;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
add(x,y+n,INF,z);
}add(s,1,INF,0);
add(n<<1,t,INF,0);
dnc();
printf("%d\n",mct);
return 0;
}
然后是方案二(缩点后)代码↓
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=2e9;
int n,m,x,y,z,np=1,s,t,mct;
int h[1005],ln[1005],q[25505];
bool vis[1005];
struct rpg{
int li,nx,ln,ct;
}a[25505];
void add(int ls,int nx,int ln,int ct){
a[++np]=(rpg){h[ls],nx,ln,ct};
h[ls]=np;
a[++np]=(rpg){h[nx],ls,0,-ct};
h[nx]=np;
}
bool spfa(){
memset(vis,0,sizeof(vis));
for(int i=0;i<=t;++i) ln[i]=INF;
int hd=1,tl=1;
q[hd]=t;
ln[t]=0;
vis[t]=1;
while(hd<=tl){
int nw=q[hd++];
vis[nw]=0;
for(int i=h[nw];i;i=a[i].li){
if(a[i^1].ln&&ln[a[i].nx]>ln[nw]-a[i].ct){
ln[a[i].nx]=ln[nw]-a[i].ct;
if(!vis[a[i].nx]){
vis[a[i].nx]=1;
q[++tl]=a[i].nx;
}
}
}
}return ln[s]<INF;
}
int dfs(int u,int maxn){
if(u==t||!maxn) return maxn;
vis[u]=1;
int sum=0;
for(int i=h[u];i;i=a[i].li){
if(a[i].ln&&!vis[a[i].nx]&&ln[a[i].nx]==ln[u]-a[i].ct){
int f=dfs(a[i].nx,min(maxn,a[i].ln));
if(f){
maxn-=f;
sum+=f;
a[i].ln-=f;
a[i^1].ln+=f;
if(!maxn) break;
}
}
}return sum;
}
void dnc(){
while(spfa()){
vis[t]=1;
while(vis[t]){
memset(vis,0,sizeof(vis));
int d=dfs(s,INF);
mct+=d*ln[s];
}
}
}
int main(){
scanf("%d%d",&n,&m);t=n+2;
for(int i=1;i<=n;++i){
scanf("%d",&x);
add(i,i+1,INF-x,0);
}for(int i=1;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
add(x,y+1,INF,z);
}add(s,1,INF,0);
add(n+1,t,INF,0);
dnc();
printf("%d\n",mct);
return 0;
}
与缩点前相比,缩点后的程序无论是时间复杂度还是空间复杂度都得到了一定的优化,建议初学者使用第一种方式构图,有一定基础的同学可以尝试第二种。