题目链接:
https://www.luogu.com.cn/problem/P3980
思路来源博客:
https://www.luogu.com.cn/blog/user9012/solution-p3980
算法:1:最小费用最大流 Dinic+Spfa
思路:
1:一开始就确定了最大流是inf,然后每一条边的流量差多少就是当天需要多少个志愿者,而志愿者有两个来源,
1:自己当天花钱请的
2:前边的天已经花过钱,今天不用再花钱,继承过来的(优先选择,因为不花钱)
2:源点连第一天,汇点连最后一天(是n+1而不是n),容量为inf费用为0
3:这样跑网络流是沿时间流的(就是依次解决每一天的问题),然后每一天向后一天连一条容量为inf-a[i](当天需要的志愿者数量)费用为0的边,为什么容量为inf-a[i]:这就相当于少了a[i],得用带权边补全确定的最大流inf,这就是志愿者连续干时沿这条边跑,因为连续干不花钱,所以优先选这种边
4:然后将每一类志愿者s[i]与t[i]+1连一条容量为inf,花费为c[i]的边,当连续干的人不够时,就得自己花钱请志愿者,补全事先确定的最大流inf
图解:
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+3,maxm=2e4+2e3+5,inf=0x7fffffff;
int n,m,s,t,tot=1,head[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn],maxflow,mincost;
//dis最小花费;pre每个点的前驱;last每个点的所连的前一条边;flow源点到此处的流量
bool vis[maxn];
queue<int>q;
struct edge
{
int to,next,w,dis;//w流量 dis花费
}e[maxm];
void addedge(int x,int y,int w,int dis)
{
e[++tot].to=y;e[tot].w=w;e[tot].dis=dis;e[tot].next=head[x];head[x]=tot;
e[++tot].to=x;e[tot].w=0;e[tot].dis=-dis;e[tot].next=head[y];head[y]=tot;
}
bool spfa(int s,int t)
{
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
while(!q.empty())
{
int now=q.front();q.pop();
vis[now]=0;
for(int i=head[now];i;i=e[i].next)
{
int y=e[i].to;
if(e[i].w>0&&dis[y]>dis[now]+e[i].dis)//相当于求最短路
{
dis[y]=dis[now]+e[i].dis;
pre[y]=now;
last[y]=i;
flow[y]=min(flow[now],e[i].w);
if(!vis[y])
{
vis[y]=1;q.push(y);
}
}
}
}
return pre[t]!=-1;
}
void dfs()
{
int now=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(now!=s)//从汇点一直回溯到源点
{
e[last[now]].w-=flow[t];
e[last[now]^1].w+=flow[t];
now=pre[now];
}
}
int main()
{
ios::sync_with_stdio(0);
scanf("%d %d",&n,&m);
s=0;t=n+2;
addedge(s,1,inf,0);
addedge(n+1,t,inf,0);
for(int i=1;i<=n;i++)
{
int a;scanf("%d",&a);
addedge(i,i+1,inf-a,0);
}
for(int i=1;i<=m;i++)
{
int x,y,dis;scanf("%d %d %d",&x,&y,&dis);
addedge(x,y+1,inf,dis);
}
while(spfa(s,t))dfs();
printf("%d\n",mincost);
return 0;
}