网络流阶段性总结
网络流,一种建图的艺术
$\ $
$\ $
显然我没有那种艺术细胞(悲
$\ $
最大流
dinic+当前弧优化
$\ $
- P1231 教辅的组成
对于书本的点数,要控制经过点的流量
因此拆点
P1231
#include
using namespace std;
const int N=4e4+10,M=1e5+10,inf=1e9;
int n1,n2,n3,m1,m2,s,t;
int cur[N],vis[N],dis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
void add(int x,int y,int z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
bool bfs(){
for(int i=1;i<=t;i++)dis[i]=0,cur[i]=head[i];
queueq;
dis[s]=1;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!dis[y]&&edge[i]){
dis[y]=dis[x]+1;
q.push(y);
}
}
}
return (dis[t]!=0);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(dis[y]==dis[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
scanf("%d%d%d",&n1,&n2,&n3);
s=n1+n1+n2+n3+1,t=n1+n1+n2+n3+2;
scanf("%d",&m1);
for(int i=1,x,y;i<=m1;i++){
scanf("%d%d",&x,&y);
add(x+n2,y,0),add(y,x+n2,1);
}
scanf("%d",&m2);
for(int i=1,x,y;i<=m2;i++){
scanf("%d%d",&x,&y);
add(x+n2+n1,y+n1+n2+n1,1),add(y+n1+n2+n1,x+n2+n1,0);
}
for(int i=1;i<=n1;i++){
add(i+n2,i+n2+n1,1),add(i+n2+n1,i+n2,0);
}
for(int i=1;i<=n2;i++){
add(s,i,1),add(i,s,0);
}
for(int i=1;i<=n3;i++){
add(i+n1+n2+n1,t,1),add(t,i+n1+n2+n1,0);
}
int flow=0;
while(bfs()){
flow+=dinic(s,inf);
}
printf("%d\n",flow);
return 0;
}
$\ $
- P1345 [USACO5.4] 奶牛的电信Telecowmunication
拆成出边点和入边点,控制点的流量为 \(1\)
路径会通过相连的信息建立联系,路径上的每个点都先走到入点,经过流量为 \(1\) 的控制边,再走到出点,保证了流量合法
P1345
#include
using namespace std;
const int N=410,M=2010,inf=1e9;
int n,m,c1,c2;
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
int cur[N],d[N];
void add(int x,int y,int z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
bool bfs(){
for(int i=1;i<=n*2;i++)cur[i]=head[i],d[i]=0;
queueq;
d[c1]=1;
q.push(c1);
while(q.size()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!d[y]&&edge[i]){
d[y]=d[x]+1;
q.push(y);
}
}
}
return (d[c2]!=0);
}
int dinic(int x,int limit){
if(x==c2||!limit)return limit;
int flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&c1,&c2);
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add(x+n,y,inf),add(y,x+n,0);
add(y+n,x,inf),add(x,y+n,0);
}
for(int i=1;i<=n;i++){
if(i!=c1&&i!=c2)add(i,i+n,1),add(i+n,i,0);
else add(i,i+n,inf),add(i+n,i,0);
}
int flow=0;
while(bfs()){
flow+=dinic(c1,inf);
}
printf("%d\n",flow);
return 0;
}
$\ $
最小割
最后的网络中,与S相连的相当于选进 \(S\) 集合,与 \(T\) 相连的则是选进 \(T\) 集合
最小割是分成两个集合的最小割边代价
$\ $
- P1361 小M的作物
把每个植物组合作为两个新点。\(S\) 向新点\(1\)连流量 \(c_{i,1}\) 的边,新点 \(2\) 向 \(T\) 连流量 \(c_{i,2}\) 的边,表示不选进这两个集合的话分别少收获多少贡献。
新点 \(1\) 向相应的作物点连不会被割掉的流量为 \(inf\) 的边,同样地,作物向新点 \(2\) 连不会被割掉的边。
如果某个组合连向 \(S\) 的边没有被割掉,那么组合中所有作物连向 \(T\) 的点都被割掉了。反之同理。
P1361
#include
#define ll long long
using namespace std;
const int N=1e5+10,M=3e6+10;
const ll inf=1e18;
int n,s,t,m,cnt,cur[N],d[N];
ll a[N],b[N],edge[M],sum;
int ver[M],head[N],tot=1,Next[M];
void add(int x,int y,ll z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
bool bfs(){
for(int i=1;i<=cnt;i++)d[i]=0,cur[i]=head[i];
d[s]=1;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!d[y]&&edge[i]){
d[y]=d[x]+1;
q.push(y);
}
}
}
return (d[t]!=0);
}
ll dinic(int x,ll limit){
if(x==t||!limit)return limit;
ll flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
scanf("%d",&n);
s=n+1,t=cnt=n+2;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
add(s,i,a[i]),add(i,s,0);
sum+=a[i];
}
for(int i=1;i<=n;i++){
scanf("%lld",&b[i]);
add(i,t,b[i]),add(t,i,0);
sum+=b[i];
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
int k,c1,c2;
scanf("%d%d%d",&k,&c1,&c2);
sum+=c1+c2;
add(s,cnt+1,c1),add(cnt+1,s,0);
add(cnt+2,t,c2),add(t,cnt+2,0);
for(int j=1,x;j<=k;j++){
scanf("%d",&x);
add(cnt+1,x,inf),add(x,cnt+1,0);
add(x,cnt+2,inf),add(cnt+2,x,0);
}
cnt+=2;
}
ll flow=0;
while(bfs()){
flow+=dinic(s,inf);
}
printf("%lld\n",sum-flow);
return 0;
}
- P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
最小割连边的板子模型之一
\(S\) 投 \(0\),\(T\) 投 \(1\),每个人向自己的意愿连边,朋友之间连流量为 \(1\) 的边。
要么割意愿边,满足朋友投了同一种票;要么割朋友边,满足自己的意愿但发生冲突。
P2057
#include
using namespace std;
const int N=2010,M=2e5+10,inf=1e9;
int n,m,s,t,ans,cnt,cur[N],d[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
void add(int x,int y,int z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=z;
}
bool bfs(){
for(int i=1;i<=cnt;i++)cur[i]=head[i],d[i]=0;
d[s]=1;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!d[y]&&edge[i]){
d[y]=d[x]+1;
q.push(y);
}
}
}
return (d[t]!=0);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
scanf("%d%d",&n,&m);
s=n+1,t=cnt=n+2;
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
if(x==1)add(i,t,1);
else add(s,i,1);
}
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y,1),add(y,x,1);
}
int flow=0;
while(bfs()){
flow+=dinic(s,inf);
}
printf("%d\n",flow);
return 0;
}
- P2762 太空飞行计划问题
源点向实验连边,实验向对应仪器连边,仪器向汇点连边,初始统计所有实验的价值
割掉实验表示不选这场实验,割掉仪器表示付出仪器的代价完成实验
P2762
#include
#define ll long long
using namespace std;
const int N=2e5+10;
const ll inf=1e18;
int m,n,c[N],s,t,d[N],cur[N],p[N],a[N],cnt,vis[N];
int ver[N<<1],head[N],tot=1,Next[N<<1];
ll edge[N<<1],ans;
void add(int x,int y,ll z){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
}
bool bfs(){
for(int i=1;i<=t;i++)cur[i]=head[i],d[i]=0;
d[s]=1;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(!d[y]&&edge[i]){
d[y]=d[x]+1;
q.push(y);
}
}
}
return (d[t]!=0);
}
ll dinic(int x,ll limit){
if(x==t||!limit)return limit;
ll flow=0,f;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(d[y]==d[x]+1&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
if(!limit)break;
}
}
return flow;
}
int main()
{
scanf("%d%d",&m,&n);
s=n+m+1,t=n+m+2;
for(int i=1;i<=m;i++){
scanf("%d",&c[i]);
ans+=c[i];
add(s,i,c[i]);
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用
{//tool是该实验所需仪器的其中一个
//这一行,你可以将读进来的编号进行储存、处理,如连边。
add(i,m+tool,inf);
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
}
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
add(i+m,t,p[i]);
// printf("i:%d p:%d\n",i,p[i]);
}
ll flow=0;
while(bfs()){
flow+=dinic(s,inf);
}
for(int i=1;i<=m;i++)if(d[i])printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++)if(d[m+i])printf("%d ",i);
printf("\n%lld\n",ans-flow);
return 0;
}
$\ $
费用流
用SPFA代替bfs的部分,\(d[y]=d[x]+1\)改成\(d[y]=(d[x]+cost[i])_{min}\)
因为有负边权,所以在 \(dinic\) 里也要注意用 \(vis\) 来标记该点是否在栈中
$\ $
P3381
#include
using namespace std;
const int N=5e3+10,M=5e4+10,inf=2147483647;
int n,m,s,t,ans,cur[N],d[N],vis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1],cost[M<<1];
void add(int x,int y,int z,int c){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
cost[tot]=c;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
cost[tot]=-c;
}
bool bfs(){
for(int i=1;i<=n;i++)cur[i]=head[i],d[i]=inf,vis[i]=0;
queueq;
d[s]=1;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(d[y]>d[x]+cost[i]&&edge[i]){
d[y]=d[x]+cost[i];
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return (d[t]!=inf);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
vis[x]=1;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(!vis[y]&&d[y]==d[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
ans+=f*cost[i];
if(!limit)break;
}
}
vis[x]=0;
return flow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1,x,y,z,c;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&z,&c);
add(x,y,z,c);
}
int flow=0;
while(bfs()){
flow+=dinic(s,inf);
}
printf("%d %d\n",flow,ans);
return 0;
}
$\ $
- P4452 [国家集训队] 航班安排
每个请求只能执行一次,所以把请求拆点,控制流量为 \(1\)。如果从0机场到一个请求起始机场的时间和从这个请求结束机场到 \(0\) 机场的时间都在 \(0-t\) 的时间范围内,则把该请求的点纳入建图。
同时,很多请求可能互相不冲突,执行完一个请求以后可以去执行另一个。因此对于不同请求之间互相进行类似的判断并连边。
把收入和花费都取相反数,跑最小费用最大流,最后答案取反得最大收入。
P4452
#include
#define ll long long
using namespace std;
const int N=2010,M=1e6+10;
const ll inf=1e18;
ll ans,dis[N],edge[M],cost[M];
int cur[N],vis[N];
int ver[M],tot=1,head[N],Next[M];
int n,m,k,t,T[N][N],F[N][N],cnt,S,E,s0;
int S0[N],T0[N],A[N],B[N],C[N],tag[N],fir[N],en[N];
void add(int x,int y,ll z,ll c){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
cost[tot]=c;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
cost[tot]=-c;
}
bool bfs(){
for(int i=1;i<=cnt;i++)cur[i]=head[i],vis[i]=0,dis[i]=inf;
dis[S]=0;
queueq;
q.push(S);
while(q.size()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(dis[y]>dis[x]+cost[i]&&edge[i]){
dis[y]=dis[x]+cost[i];
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return (dis[E]!=inf);
}
ll dinic(int x,ll limit){
if(!limit||x==E)return limit;
ll flow=0,f;
vis[x]=1;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,1ll*edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
ans+=cost[i]*f;
if(!limit)break;
}
}
vis[x]=0;
return flow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&k,&t);
S=1,E=2,cnt=s0=3;
add(S,s0,k,0);
for(int i=0;it||T[0][A[i]]>S0[i])continue;
tag[i]=1;
add(s0,cnt+1,inf,F[0][A[i]]);
add(cnt+1,cnt+2,1,-C[i]);
add(cnt+2,E,inf,F[B[i]][0]);
fir[i]=cnt+1,en[i]=cnt+2;
cnt+=2;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
if(i==j||!tag[i]||!tag[j])continue;
if(T0[i]+T[B[i]][A[j]]<=S0[j])add(en[i],fir[j],inf,F[B[i]][A[j]]);
}
}
ll flow=0;
while(bfs()){
flow+=dinic(S,inf);
}
printf("%lld\n",-ans);
return 0;
}
$\ $
- P2153 [SDOI2009] 晨跑
每个点都只能走一次,所以拆点,然后跑最小费用最大流
注意如果有 \(1→n\) 的边,这条边只能走一次。
P2153
#include
#define ll long long
using namespace std;
const int N=510,M=4e4+10,inf=1e9+10;
const ll Inf=1e18;
int n,m,s,t,cnt;
int cur[N],vis[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
ll cost[M<<1],ans,dis[N];
void add(int x,int y,int z,int c){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
cost[tot]=c;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
cost[tot]=-c;
}
bool bfs(){
for(int i=1;i<=n*2;i++)dis[i]=Inf,vis[i]=0,cur[i]=head[i];
queueq;
q.push(1);
dis[1]=0;
while(q.size()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(dis[y]>dis[x]+cost[i]&&edge[i]){
dis[y]=dis[x]+cost[i];
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return (dis[n]!=Inf);
}
int dinic(int x,int limit){
if(x==n||!limit)return limit;
int flow=0,f;
vis[x]=1;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
ans+=f*cost[i];
if(!limit)break;
}
}
vis[x]=0;
return flow;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)add(i,i+n,(i==1||i==n)?inf:1,0);
for(int i=1,a,b,c;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
add(a+n,b,(a==1&&b==n)?1:inf,c);
}
int flow=0;
while(bfs()){
flow+=dinic(1,inf);
}
printf("%d %lld\n",flow,ans);
return 0;
}
$\ $
- P2053 [SCOI2007] 修车
发现在某个技术人员这里,倒数第一辆被修的车会有一个人在等,倒数第二辆被修的车会有两个人在等……倒数第 \(k\) 辆被修的车对应 \(k*t_{i,j}\)的等待时间。同时,每个技术人员修的车中,只会有一辆是倒数第一辆,只会有一辆是倒数第二辆……
把每个技术人员拆成 \(n\) 个点,表示修倒数第 \(k\) 辆车,同时控制点的流量为 \(1\)(同样是拆点),每个出点向汇点连边。每辆车分别向每个技术人员的 \(k\) 个点连边,表示这辆车 \(i\) 被某个技术人员 \(j\) 作为倒数第 \(k\) 辆修,花费就是等待时间 \(k*t_{i,j}\)。
跑最小费用最大流,最后的答案 \(/n\) 就是平均等待时间。
对于这种同一时间只能处理一个事项,需要排队,并且处理每个事项所占的时间/空间是独一无二的,感觉比较常用这种每个处理器拆成目标个数个点的处理方法。
P2053
#include
#define ll long long
using namespace std;
const int N=110,inf=1e9+10;
const ll Inf=1e18;
int n,m,s,t,fir[N][N],lst[N][N],cnt,cur[N*N];
ll ans,cost[N*N*N],dis[N*N],vis[N*N];
int ver[N*N*N],head[N*N],tot=1,Next[N*N*N],edge[N*N*N];
void add(int x,int y,int z,int c){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
cost[tot]=c;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
cost[tot]=-c;
}
bool bfs(){
for(int i=1;i<=cnt;i++)vis[i]=0,cur[i]=head[i],dis[i]=Inf;
dis[s]=0;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(dis[y]>dis[x]+cost[i]&&edge[i]){
dis[y]=dis[x]+cost[i];
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return (dis[t]!=Inf);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
vis[x]=1;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
ans+=f*cost[i];
if(!limit)break;
}
}
vis[x]=0;
return flow;
}
int main()
{
scanf("%d%d",&m,&n);
s=n+1,t=cnt=n+2;
for(int i=1;i<=n;i++)add(s,i,1,0);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
fir[i][j]=++cnt,lst[i][j]=++cnt;
add(fir[i][j],lst[i][j],1,0);
add(lst[i][j],t,inf,0);
}
}
for(int i=1;i<=n;i++){
for(int j=1,x;j<=m;j++){
scanf("%d",&x);
for(int k=1;k<=n;k++){
add(i,fir[j][k],inf,x*k);
}
}
}
while(bfs())dinic(s,inf);
printf("%.2lf\n",(double)ans/n);
return 0;
}
$\ $
- P2517 [HAOI2010] 订货
为数不多100%自己搞出来的题,虽然是个板子TvT
每个月都能订货且费用不同,所以从源点向每个月的点连流量 \(INF\),费用为订货单价的边。每个月有库存容量和单位储存花费,那么每个月向下一个月连流量 \(S\),费用为单位储存花费的边。最后每个月有个需求量,那么每个月的点向汇点连流量为需求量的边。
P2517
#include
#define ll long long
using namespace std;
const int N = 210, M = 5010, inf = 1e9 + 10;
const ll INF = 1e16;
int n, m, S, x, s, t;
int cur[N], vis[N];
int ver[M << 1], head[N], tot = 1, Next[M << 1], edge[M << 1];
ll cost[M << 1], dis[M << 1], ans;
void add(int x, int y, int z, int c) {
ver[++tot] = y;
Next[tot] = head[x];
head[x] = tot;
edge[tot] = z;
cost[tot] = c;
ver[++tot] = x;
Next[tot] = head[y];
head[y] = tot;
edge[tot] = 0;
cost[tot] = -c;
}
bool bfs() {
for (int i = s;i <= t;i++)cur[i] = head[i], dis[i] = INF, vis[i] = 0;
dis[s] = 0;
queueq;
q.push(s);
while (q.size()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (int i = head[x];i;i = Next[i]) {
int y = ver[i];
if (edge[i] && dis[y] > dis[x] + cost[i]) {
dis[y] = dis[x] + cost[i];
if (!vis[y]) {
vis[y] = 1;
q.push(y);
}
}
}
}
return (dis[t] != INF);
}
int dinic(int x, int limit) {
if (x == t || !limit)return limit;
int flow = 0, f;
vis[x] = 1;
for (int& i = cur[x];i;i = Next[i]) {
int y = ver[i];
if (!vis[y] && dis[y] == dis[x] + cost[i] && (f = dinic(y, min(limit, edge[i])))) {
flow += f, limit -= f;
edge[i] -= f, edge[i ^ 1] += f;
ans += f * cost[i];
if (!limit)break;
}
}
vis[x] = 0;
return flow;
}
int main()
{
scanf("%d%d%d", &n, &m, &S);
s = 0, t = n + 1;
for (int i = 1;i <= n;i++) {
scanf("%d", &x);
if (i < n)add(i, i + 1, S, m);
add(i, t, x, 0);
}
for (int i = 1;i <= n;i++) {
scanf("%d", &x);
add(s, i, inf, x);
}
while (bfs())dinic(s, inf);
printf("%lld\n", ans);
return 0;
}
$\ $
- P2050 [NOI2012] 美食节
和修车那题相似,也是一道涉及排队和处理任务的题。不过这题如果直接把所有点都建出来会达到恐怖的点数,因此在过程中动态加点。
如果在某遍 \(dinic\) 之后,某个厨师拆出来最靠后的点满流,那么对于这个厨师再拆出一个等待时间 \(+1\) 份的新点。\((k→k+1)\)
P2517
#include
#define ll long long
using namespace std;
const ll INF=1e18;
const int N=4e5+10,M=2e6+10,inf=1e9;
ll cost[M<<1],ans,dis[N];
int cur[N],vis[N],pos[N],tim[210][210],siz[N];
int ver[M<<1],head[N],tot=1,Next[M<<1],edge[M<<1];
int n,m,s,t,num,cnt;
void add(int x,int y,int z,int c){
ver[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
cost[tot]=c;
ver[++tot]=x;
Next[tot]=head[y];
head[y]=tot;
edge[tot]=0;
cost[tot]=-c;
}
bool bfs(){
for(int i=1;i<=cnt;i++)dis[i]=INF,vis[i]=0,cur[i]=head[i];
dis[s]=0;
queueq;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(dis[y]>dis[x]+cost[i]&&edge[i]){
dis[y]=dis[x]+cost[i];
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return (dis[t]!=INF);
}
int dinic(int x,int limit){
if(x==t||!limit)return limit;
int flow=0,f;
vis[x]=1;
for(int &i=cur[x];i;i=Next[i]){
int y=ver[i];
if(!vis[y]&&dis[y]==dis[x]+cost[i]&&(f=dinic(y,min(limit,edge[i])))){
flow+=f,limit-=f;
edge[i]-=f,edge[i^1]+=f;
ans+=f*cost[i];
if(!limit)break;
}
}
vis[x]=0;
return flow;
}
int main()
{
scanf("%d%d",&n,&m);
s=n+1,t=cnt=n+2;
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
num+=x;
add(s,i,x,0);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&tim[i][j]);
}
}
for(int i=1;i<=m;i++){
add(cnt+1,cnt+2,1,0);
pos[i]=tot-1;
add(cnt+2,t,inf,0);
for(int j=1;j<=n;j++){
add(j,cnt+1,inf,tim[j][i]);
}
siz[i]=1;
cnt+=2;
}
while(bfs()){
dinic(s,inf);
for(int i=1;i<=m;i++){
if(!edge[pos[i]]){
add(cnt+1,cnt+2,1,0);
pos[i]=tot-1;
siz[i]++;
add(cnt+2,t,inf,0);
for(int j=1;j<=n;j++){
add(j,cnt+1,inf,tim[j][i]*siz[i]);
}
cnt+=2;
}
}
}
printf("%lld\n",ans);
return 0;
}
继续学习ing……