1. 程式人生 > 其它 >2022.02.23 網路流複習

2022.02.23 網路流複習

2022.02.23 網路流複習

https://www.cnblogs.com/Miracevin/p/11245896.html

https://www.cnblogs.com/Miracevin/p/10028021.html

https://www.cnblogs.com/Point-King/p/15724247.html

1. 費用流

1.1 關於費用流的理解

https://www.cnblogs.com/Miracevin/p/10028021.html

1.1.1 費用流

費用流分為兩種:

  1. 最小費用最大流
  2. 最大費用最大流

可以看出,費用流是建立在最大流的基礎上進行的騷操作。

2.1 練習題

2.1.1 P2153 [SDOI2009]晨跑
(經典標誌:在週期最長的情況下,總路程最短)(拆點為邊限制經過次數)

https://www.luogu.com.cn/problem/P2153

根據:在週期最長的情況下,總路程最短,可以判斷出這是道費用流的題,而不是最大流。

週期就是流量,路程就是費用,正好滿足最小費用最大流。

對於一個點只能用一遍,所以要把一個點拆成兩個點i與i+n,從i到i+n連一條容量為1費用為0的邊,保證從它自己到它自己費用為0。對於從x到y有一條路,從x+n到y建一條容量為1費用為距離的邊。因為寢室與學校不算十字路口,不用滿足只經過一次的要求,所以S是1+你,T是n。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=410;
const int M=6e4+10;
const int inf=1<<30;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=0x3f3f3f3f;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++)add(i,i+n,1,0);
	for(Ri int i=1;i<=m;i++){
		int u,v,w;
		u=read();v=read();w=read();
		add(u+n,v,1,w);
	}
	S=1+n,T=n;
	Ek(S,T);
	cout<<maxnflow<<" "<<maxncost;
	return 0;
}

2.1.2 P2604 [ZJOI2010]網路擴容(費用流與最大流之間的轉換)

https://www.luogu.com.cn/problem/P2604

簡而言之就是:如果費用流的費用是0,跑出來的manflow就是最大流。——來自某天我的想法

今天我才第一次肯定。——參照1.1.1

——2022.02.23 8:49 eleveni

而且這道題在殘量網路上不一定對,因為最大流不止一種跑法,很有可能第一遍算出來的最大流的方案並不是增加k之後費用流的最優解方案。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=5e3+10;
const int M=4e4+10;
const int inf=0x3f3f3f3f;
int n,m,k,cnt=1,head[N],dis[N],flow[N],pre[N],vis[N],val[N],from[N],toi[N],cap[N];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].next=head[u];
	a[cnt].val=w;
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];maxncost+=flow[t]*dis[t];
}
inline void Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();k=read();
	for(Ri int i=1;i<=m;i++){
		int u,v,w,x;
		from[i]=u=read();toi[i]=v=read();cap[i]=w=read();val[i]=read();
		add(u,v,w,0);
	}
	maxnflow=0;maxncost=0;
	Ek(1,n);
	cout<<maxnflow<<" ";
	/*for(Ri int i=2;i<=cnt;i+=2){
		a[i].val+=a[i^1].val;
		a[i^1].val=0;
	}*/
	cnt=1;
	memset(head,0,sizeof(head));
	memset(a,0,sizeof(a));
	for(Ri int i=1;i<=m;i++){
		add(from[i],toi[i],inf,val[i]);
		add(from[i],toi[i],cap[i],0);
	}
	S=n+1,T=n;
	add(S,1,k+maxnflow,0);
	maxnflow=0;maxncost=0;
	Ek(S,T);
	//cout<<maxnflow<<" ";
	cout<<maxncost;
	return 0;
}

2.1.3 P2050 [NOI2012] 美食節(動態加邊+按階段拆點)

https://www.luogu.com.cn/problem/P2050

https://www.cnblogs.com/Miracevin/p/9715428.html

如果像修車那道題一樣一次性建完邊的話,嚴重懷疑會TLE,但是我想試一試。

所以先介紹一下一次性建完邊的寫法:

從S到每種菜建一條容量為\(P_i\) 費用為0的邊,表示一共要做\(P_i\)個這種菜。把每個廚師拆分成\(Ptot\)個點,廚師i在階段j表示這個廚師在做倒數第j道菜。從菜品i到第j個廚師的階段k連一條容量為1費用為\(t_{i,j}*k\)的邊,做這道菜對後面總共等待時間的貢獻是\(k*t_{i,j}\)。從每個廚師每個階段向T連一條容量為1費用為0的邊。

試出來,60pts,還可以。

60pts(沒有動態加邊)
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=5e4+10;
const int M=1e7+10;
const int inf=0x3f3f3f3f;
int n,m,cnt=1,head[N],dis[N],vis[N],pre[N],flow[N],tot,num[N];
int timei[50][110];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].cost=x;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	//cout<<dis[1]<<endl;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;vis[s]=1;flow[s]=inf;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		//cout<<"x "<<x<<endl;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void Ek(int s,int t){
	while(spfa(s,t))update(s,t);//,cout<<"Case 1"<<endl;
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++)num[i]=read(),tot+=num[i];
	S=n+m*tot+1;T=n+m*tot+2;
	for(Ri int i=1;i<=n;i++)add(S,i,num[i],0);//,cout<<S<<" "<<i<<endl;
	for(Ri int i=1;i<=n;i++)
	for(Ri int j=1;j<=m;j++)timei[i][j]=read();
	for(Ri int i=1;i<=n;i++)
	for(Ri int j=1;j<=m;j++)
	for(Ri int k=1;k<=tot;k++)
	add(i,(j-1)*tot+k+n,1,timei[i][j]*k);//,cout<<i<<" "<<(j-1)*tot+k+n<<endl;
	for(Ri int i=1;i<=m;i++)
	for(Ri int j=1;j<=tot;j++)
	add((i-1)*tot+j+n,T,1,0);//,cout<<(i-1)*tot+j+n<<" "<<T<<endl;
	Ek(S,T);
	cout<<maxncost;
	return 0;
}

說明還是必須動態加邊的。當前廚師只有第j個階段被佔用了才會進入下一個j+1階段,可以根據這個在update的時候把被佔用的階段全部連上下一個階段。

預計得分100pts,但是邊數我就留著了,懶得修改/斜眼笑。

100pts(動態加邊)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define Ri register
const int N=7e5+10;
const int M=1e7+6e5+10;
const int inf=0x3f3f3f3f;
int n,m,cnt=1,head[N],dis[N],vis[N],pre[N],flow[N],tot,num[N];
int timei[50][110],flag[110];
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].cost=x;
	a[cnt].next=head[u];
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,inf,sizeof(dis));
	//cout<<dis[1]<<endl;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;vis[s]=1;flow[s]=inf;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		//cout<<"x "<<x<<endl;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"Case 2"<<endl;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		//cout<<"x "<<x<<endl;
		if(x>n&&x<s){
			int cooker=(x-n)/tot+((x-n)%tot!=0);
			int id=(x-n)%tot?(x-n)%tot:tot;
			++id;
			if(id<=flag[cooker]||id>tot){
				x=a[xi^1].to;
				continue;
			}
			flag[cooker]=id;
			for(Ri int i=1;i<=n;i++)
			add(i,(cooker-1)*tot+id+n,1,timei[i][cooker]*id);
			//cout<<i<<" "<<(cooker-1)*tot+id+n<<endl;
			add((cooker-1)*tot+id+n,T,1,0);
			//cout<<(cooker-1)*tot+id+n<<" "<<T<<endl;
		}
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void Ek(int s,int t){
	while(spfa(s,t))update(s,t);//,cout<<"Case 1"<<endl;
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++)num[i]=read(),tot+=num[i];
	S=n+m*tot+1;T=n+m*tot+2;
	for(Ri int i=1;i<=n;i++)add(S,i,num[i],0);//,cout<<S<<" "<<i<<endl;
	for(Ri int i=1;i<=n;i++)
	for(Ri int j=1;j<=m;j++)timei[i][j]=read();
	/*for(Ri int i=1;i<=n;i++)
	for(Ri int j=1;j<=m;j++)
	for(Ri int k=1;k<=tot;k++)
	add(i,(j-1)*tot+k+n,1,timei[i][j]*k);//,cout<<i<<" "<<(j-1)*tot+k+n<<endl;
	for(Ri int i=1;i<=m;i++)
	for(Ri int j=1;j<=tot;j++)
	add((i-1)*tot+j+n,T,1,0);//,cout<<(i-1)*tot+j+n<<" "<<T<<endl;*/
	for(Ri int i=1;i<=n;i++)
	for(Ri int j=1;j<=m;j++)
	add(i,n+(j-1)*tot+1,1,timei[i][j]);//,cout<<i<<" "<<n+(j-1)*tot+1<<endl;
	for(Ri int i=1;i<=m;i++)
	add(n+(i-1)*tot+1,T,1,0),flag[i]=1;
	//cout<<n+(i-1)*tot+1<<" "<<T<<endl;
	Ek(S,T);
	cout<<maxncost;
	return 0;
}

2.1.4 P3980 [NOI2008] 志願者招募(一面對多面、線性規劃)

https://www.luogu.com.cn/problem/P3980

在這道題里人數是流量,僱傭員工的費用是費用……廢話!

網路流的流量增加1時不能再分叉之後所有路徑上的流量都增加1,這就難以解決每天都有的人數限制的問題。所以對於一個人,我們可以把他工作的天數全部連在一起。然後 S是第0天,T是第n+1天。反正也去不掉多餘的員工,那就讓他一直跑到結束為止,但是跑過自己負責時間的員工不能佔新花錢買來的員工,所以對於每類員工都從他們開始工作那天到他們工作結束的後一天連一條容量inf費用是僱傭他們價錢的邊;對於每一天需要多少人可以讓i到i+1來確定,從i到i+1建一條容量為現在還不確定費用為0的邊,畢竟現在這些多餘的員工只是陪跑。我們想讓員工的缺口為\(a_i\),但是網路流流量不能是負數,所以就整體增加一個maxn值,這樣一張真正的圖就可以建出來了:

從S到1建一條容量為maxn費用為0的邊,從n到T建一條容量為inf費用為0的邊,表示假設有這麼多虛擬小人兒在跑圖;從i到i+1建一條容量為\(-a_i+maxn\)費用為\(c_i\)的邊,表示第i天只能有\(-a_i+maxn\)個虛擬小人,其他的跑圖小人和將要收費小人的個數必須是\(a_i\),其他的正在工作的跑圖小人不需要收費,收費小人必須要收費,所以從\(s_i\)\(t_i+1\)建一條容量為inf費用為\(c_i\)的邊來收費。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=1010;
const int M=6e4+10;
const int maxn=1e7;
typedef long long ll;
const ll inf=1e18;
int n,m,cnt=1,head[N],vis[N],pre[N];
int S,T;
ll dis[N],flow[N];
ll maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	for(Ri int i=0;i<=n+8;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+1ll*a[i].cost;
				flow[v]=min(flow[x],1ll*a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=(int)flow[t];
		a[xi^1].val+=(int)flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=1ll*flow[t];
	maxncost+=1ll*flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	for(Ri int i=1;i<=n;i++){
		int x=read();
		add(i,i+1,maxn-x,0);
	}
	for(Ri int i=1;i<=m;i++){
		int u,v,w;
		u=read();v=read();w=read();
		add(u,v+1,maxn,w);
	}
	S=0,T=n+2;add(S,1,maxn,0);add(n+1,T,maxn,0);
	Ek(S,T);
	cout<<maxncost;
	return 0;
}

2.1.5 P3705 [SDOI2017]新生舞會(神奇的算分子分母極值的題、分數規劃、二分+費用流)

https://www.luogu.com.cn/problem/P3705

對於式子\(C=\frac{\sum_a}{\sum_b}\),可以繼續推出\(\sum_a-C*\sum_b=0\) 。考慮二分出C的值,如果\(\sum_a-C*\sum_b>=0\),說明C滿足條件。

每次建圖:
從S到每個男生連一條容量為1費用為0的邊;從每個女生到T連一條容量為1費用為0的邊;從i到j連一條容量為1費用為\(a_{i,j}-C*b_{i,j}\)的邊。

如果最大費用最大流結果大於等於0,則C滿足。

不知道為什麼求最長路掛了,於是加了個負號,忽然想到spfa不就是求有負權的最短路的嗎?

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=220;
const int M=3e4+10;
const double eps=1e-8;
const int inf=1<<30;
int n,m,cnt=1,head[N],vis[N],pre[N],flow[N];
int S,T;
double dis[N],a1[110][110],b1[110][110];
int maxnflow;
double maxncost;
struct node{
	int to,next,val;
	double cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,double x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,double x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//cout<<"there is spfa "<<endl;
	for(Ri int i=0;i<=n*2+3;i++)dis[i]=(double)inf;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0.0;flow[s]=1<<30;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=(double)inf;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=(double)flow[t]*dis[t];
}
inline bool Ek(int s,int t,double C){
	maxnflow=0;
	maxncost=0.0;
	cnt=1;
	memset(head,0,sizeof(head));
	memset(a,0,sizeof(a));
	for(Ri int i=1;i<=n;i++)add(S,i,1,0),add(i+n,T,1,0);
	//cout<<S<<" "<<i<<endl<<i+n<<" "<<T<<endl;
	for(Ri int i=1;i<=n;i++)for(Ri int j=1;j<=n;j++)
	add(i,j+n,1,-a1[i][j]+C*b1[i][j]);//,cout<<i<<" "<<j+n<<endl;
	while(spfa(s,t))update(s,t);//,cout<<"there is EK "<<endl;
	//cout<<"C "<<C<<" maxncost "<<maxncost<<endl;
	return maxncost*-1.0>=0.0;
}
inline void find(){
	double L=0,R=10010,mid,ans=0;
	while(R-L>eps){
		mid=(L+R)/2.0;
		//cout<<"L "<<L<<" R "<<R<<" mid "<<mid<<" ans "<<ans<<endl;
		if(Ek(S,T,mid))ans=mid,L=mid+eps;
		else R=mid-eps;
	}
	printf("%.6lf",ans);
}

signed main(){
	n=read();
	S=n*2+1,T=n*2+2;
	for(Ri int i=1;i<=n;i++)for(Ri int j=1;j<=n;j++)a1[i][j]=read()*1.0;
	for(Ri int i=1;i<=n;i++)for(Ri int j=1;j<=n;j++)b1[i][j]=read()*1.0;
	find();
	return 0;
}

2.1.6 P2488 [SDOI2011]工作安排

https://www.luogu.com.cn/problem/P2488

這道題看起來很妙啊!別人都是對後面的有積累,這道題反而是從前面開始算。

50pts
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define int long long
#define Ri register
const int N=201000;
const int M=6e7+10;
const int inf=1e18;
const int maxn=1e7;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost,tot;
int A[360][360],sep[360][50],W[360][50],flag[360][10],C[360];
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//memset(dis,0x3f3f3f3f,sizeof(dis));
	for(Ri int i=0;i<=T;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"flow[t] "<<flow[t]<<endl;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		//cout<<x<<endl;
		if(x>n&&x<S&&a[xi].val==0){
			int emp=(x-n)/tot+((x-n)%tot!=0);
			int id=(x-n)%tot?(x-n)%tot:tot;
			int flagi=0;
			if(a[xi].val==0){
				if(sep[emp][sep[emp][0]+1]==0){
					x=a[xi^1].to;
					continue;
				}else ++sep[emp][0],++id;
			}
			if(a[xi].val!=0){
				x=a[xi^1].to;
				continue;
			}
			//cout<<"emp "<<emp<<" id "<<id<<" x "<<x<<endl;
			int toi=n+(emp-1)*tot+sep[emp][0];
			int vali=sep[emp][sep[emp][0]]-sep[emp][sep[emp][0]-1];
			for(Ri int i=1;i<=n;i++)if(A[emp][i])
			add(i,toi,C[i],W[emp][sep[emp][0]]);
			//cout<<i<<" "<<toi<<" "<<C[i]<<" "<<W[emp][sep[emp][0]]<<endl;
			if(!flag[emp][sep[emp][0]])add(toi,T,vali,0),flag[emp][sep[emp][0]]=1;
			//cout<<toi<<" "<<T<<" "<<vali<<" 0 "<<endl;
		}
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	m=read();n=read();
	for(Ri int i=1;i<=n;i++)C[i]=read();
	for(Ri int i=1;i<=m;i++)for(Ri int j=1;j<=n;j++)A[i][j]=read();
	for(Ri int i=1;i<=m;i++){
		sep[i][0]=read();
		for(Ri int j=1;j<=sep[i][0];j++)sep[i][j]=read();sep[i][sep[i][0]+1]=maxn;
		for(Ri int j=1;j<=sep[i][0]+1;j++)W[i][j]=read();
		tot=max(sep[i][0]+1,tot);
		sep[i][0]=1;
	}
	S=n+m*tot+1;T=S+1;
	//cout<<"S "<<S<<" T "<<T<<" tot "<<tot<<endl;
	for(Ri int i=1;i<=n;i++)add(S,i,C[i],0);//,cout<<S<<" "<<i<<" "<<C[i]<<" 0 "<<endl;
	for(Ri int i=1;i<=m;i++)for(Ri int j=1;j<=n;j++)if(A[i][j])
	add(j,n+(i-1)*tot+1,C[j],W[i][1]);
	//add(n+(i-1)*tot+1,T,sep[i][1],0),
	//cout<<j<<" "<<n+(i-1)*tot+1<<" "<<C[j]<<" "<<W[i][1]<<endl;
	for(Ri int i=1;i<=m;i++)add(n+(i-1)*tot+1,T,sep[i][1],0),flag[i][1]=1;
	//cout<<n+(i-1)*tot+1<<" "<<T<<" "<<sep[i][1]<<" 0 "<<endl;
	Ek(S,T);
	cout<<maxncost;
	return 0;
}

我去,忽然發現我為毛要一直動態加邊?我直接建好多邊連在物體和人上不就行了?

從S到每個人按照他給的分段函式連一條容量為\(seg_{i,j}-seg_{i,j-1}\)費用為\(W_{i,j}\)的邊;從每個物體到T連一條從容量為\(C_{i}\)費用為0的邊;從人i到物體j根據\(A_{i,j}\),連一條容量為inf費用為0的邊。

100pts
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define int long long
#define Ri register
const int N=500010;
const int M=2e7+10;
const int inf=1e18;
const int maxn=1e8;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int S,T,maxnflow,maxncost,tot;
int A[560][560],sep[560][10],W[560][10],flag[560][10],C[560];
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	//memset(dis,0x3f3f3f3f,sizeof(dis));
	for(Ri int i=0;i<=T;i++)dis[i]=inf;
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf;
}
inline void update(int s,int t){
	int x=t;
	//cout<<"flow[t] "<<flow[t]<<endl;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		//cout<<x<<endl;
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	m=read();n=read();
	S=n+m+1;T=S+1;
	for(Ri int i=1;i<=n;i++){
		int x=read();
		add(i+m,T,x,0);
	}
	for(Ri int i=1;i<=m;i++)for(Ri int j=1;j<=n;j++){
		A[i][j]=read();
		if(A[i][j])add(i,j+m,maxn,0);
		
	}
	for(Ri int i=1;i<=m;i++){
		int x=read();
		for(Ri int j=1;j<=x;j++)sep[i][j]=read();sep[i][x+1]=maxn;
		for(Ri int j=1;j<=x+1;j++)W[i][j]=read(),add(S,i,sep[i][j]-sep[i][j-1],W[i][j]);
	}
	Ek(S,T);
	cout<<maxncost;
	return 0;
}

2.1.7 P7730 [JDWOI-1] 蜀道難(本題涉及差分、正負邊權分別處理)

https://www.luogu.com.cn/problem/P7730

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;

#define Ri register
const int N=5e4+10;
const int M=1e5+10;
const int inf=1<<30;
int n,m,cnt=1,head[N],dis[N],flow[N],vis[N],pre[N];
int op[2][N],edge[N],ai[N],b[N],top;
int S,T,maxnflow,maxncost;
struct node{
	int to,next,val,cost;
}a[M];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
} 
inline void addi(int u,int v,int w,int x){
	++cnt;
	a[cnt].to=v;
	a[cnt].val=w;
	a[cnt].next=head[u];
	a[cnt].cost=x;
	head[u]=cnt;
}
inline void add(int u,int v,int w,int x){
	addi(u,v,w,x);addi(v,u,0,-x);
}
inline int spfa(int s,int t){
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	dis[s]=0;flow[s]=inf;
	q.push(s);vis[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(Ri int i=head[x];i;i=a[i].next){
			int v=a[i].to;
			if(dis[v]>dis[x]+a[i].cost&&a[i].val>0){
				dis[v]=dis[x]+a[i].cost;
				flow[v]=min(flow[x],a[i].val);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=0x3f3f3f3f;
}
inline void update(int s,int t){
	int x=t;
	while(x!=s){
		int xi=pre[x];
		a[xi].val-=flow[t];
		a[xi^1].val+=flow[t];
		x=a[xi^1].to;
	}
	maxnflow+=flow[t];
	maxncost+=flow[t]*dis[t];
}
inline void  Ek(int s,int t){
	while(spfa(s,t))update(s,t);
}

signed main(){
	n=read();m=read();
	S=n+2,T=n+3;
	for(Ri int i=1;i<=n;i++)ai[i]=read(),b[i]=ai[i]-ai[i-1];
	for(Ri int i=0;i<=n+1;i++)op[0][i]=op[1][i]=inf;
	for(Ri int i=1;i<=m;i++){
		int v,w;
		char x;cin>>x;
		v=read();w=read();
		if(x=='+')op[0][v]=min(op[0][v],w);
		else op[1][v]=min(op[1][v],w);
	}
	add(S,n+1,inf,0);
	for(Ri int i=1;i<=n;i++)
	if(b[i]>0)add(S,i,b[i],0);
	else add(i,T,-b[i],0),edge[++top]=cnt-1;
	for(Ri int i=1;i<=n;i++)for(Ri int l=1,r=l+i;r<=n+1;l++,r=l+i){
		if(op[0][i]<inf)add(r,l,inf,op[0][i]);
		if(op[1][i]<inf)add(l,r,inf,op[1][i]);
	}
	Ek(S,T);
	for(int i=1;i<=top;i++)if(a[edge[i]].val)return puts("-1"),0;
	cout<<maxncost;
	return 0;
}