ASC11 A - Beer Problem

题意:给出一个无向网络,求其最大费用流(不是最大费用最大流)

首先考虑无向图怎么解决。

先尝试对每个边构造一个子结构,具体方法是对每个边 \((x,y)\) 新增两个点 \(a,b\),然后从 \((x,y)\) 分别向 \(a\) 连有向边,\(b\)\((x,y)\) 连有向边。\(a\)\(b\) 之间连流量为 \(f\),费用为 \(c\) 的边。这样就和无向边是一样的。

但是这会让我们的点数变成 \(O(m)\) 的。我们发现一条边其实不可能从两边都流哪怕一个流量。因为我们把两边的流量互换,因为边权是负的,所以两边都不流这个流量一定是更优的。所以我们可以直接两边连流量费用 \((f,c)\) 的有向边。但是只有最大费用流且边权是负(或最小费用流边权是正)的时候可以这么干。

然后考虑解决最大费用流。首先众所周知在非满流的情况下,费用是关于流量的凸函数。我们通过暴力计算的方法求出了在样例中不同的流量和费用的对应关系式。

(这是流量和最终答案的关系,最终答案是网络流中费用的相反数)

image

横坐标是源点流出的流量,纵坐标是费用 \(/100\)。而我们只要最大费用。我们发现,满流在 \(f=120\) 取到,这时的费用为 \(c=2900\)。在这之前,费用是单峰的且是凸的。

既然是个凸函数,就可以通过限制从源点出发的流量,然后三分法找到费用函数的顶点。

三分法的三分和建图代码如下:


int cnt=0,n,m,a[2005],b[2005],c[2005],f[2005],p[105];
inline void add_edge(int x,int y,int f,int c){
	Juc::add_edg(y,x,f,c);
	Juc::add_edg(x,y,f,c);
}
inline int check(int flow){
	Juc::flush();
	cnt=n+1;
	rep(i,1,n+1)Juc::ptt(i);
	Juc::s=0,Juc::t=n+1;
	Juc::add_edg(0,1,flow,0);
	rep(i,2,n)Juc::add_edg(i,n+1,1e9,-p[i]);
	rp(i,m)add_edge(a[i],b[i],f[i],c[i]);
	Juc::MCMF();
	return -Juc::cost;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	rep(i,2,n)cin>>p[i];
	rp(i,m)cin>>a[i]>>b[i]>>f[i]>>c[i];
	int l=0,vl=check(0),r=100000,vr=check(r),ans=max(vl,vr);
	while(r-l>3){
		int len=(r-l-1),x=l+len/3,y=r-len/3;
		int vx=check(x),vy=check(y);
		ans=max(ans,vx);
		ans=max(ans,vy);
		if(vx>=vy)r=y,vr=vy;
		else l=x,vl=vx;
	}
	rep(i,l+1,r-1)ans=max(ans,check(i));
	cout<<ans<<endl;
	return 0;
}
//Nyn-siyn-hert

可惜这样复杂度是寄的。

网络流最有有效的优化是什么呢?是残量网络优化。我们每次给源点加一的流量,然后就在残量网络跑一遍 MCMF,观察当前的费用是否比前一次更好。如果更劣了,峰就在前一个达到了,直接输出即可。但是这样相当于把费用流那个很松的 \(f\) 给卡满了,复杂度就是满的 \(O(nmf)\),也是会挂的。

	int ans=0,res=0;
	rep(i,1,100000){
		Juc::add_edg(0,1,1,0);
		Juc::MCMF();
		res=-Juc::cost;
		if(res<=ans)break;
		ans=res;
	}cout<<ans<<endl;

我们考虑从网络流本身入手。为什么网络流是凸函数呢?这和 \(Dinic\) 算法的内核有关。\(Dinic\) 算法的内核是每次沿着最短路增广,而当前流的费用就是最短路的长度。而 \(Dinic\) 的复杂度证明里面有一个“最短路不变小”。所以,实际上我们的“费用函数的导数”,也就是“每次增广之后的最短路”是单调的。所以网络流才是凸函数。

所以很明显,当我们的 \(spfa\) 跑出正的最短路的时候,我们就该发现,现在的费用函数已经来到了顶点,可以直接返回答案了。

这样就可以解决这个题。


namespace Juc{
	int n,m,s,t,a,b,c,d;
	vt<int>pnts;
	int dep[200005],vis[200005],cur[200005],dist[200005],vist[200005];
	struct edge{
		int a,b,f,c;
		edge(int _a,int _b,int _f,int _c){
			a=_a,b=_b,f=_f,c=_c;
		};
	};
	vt<edge>ve;
	vt<int>vv[200005];
	inline void add_edg(int a,int b,int c,int d){
		ve.pb(edge(a,b,c,d));
		ve.pb(edge(b,a,0,-d));
		vv[a].pb(ve.size()-2);
		vv[b].pb(ve.size()-1);
	}
	inline void ptt(int a){
		pnts.pb(a);
	}
	inline bool SPFA(){
		for(auto i:pnts)dist[i]=1e9,vist[i]=0,vis[i]=0,cur[i]=0;
		queue<int>q;
		q.push(s),vist[s]=1,dist[s]=0;
		while(q.size()){
			int i=q.front();
			q.pop();
			vist[i]=0;
			for(auto g:vv[i]){
				auto &e=ve[g];
				if(e.f&&dist[i]+e.c<dist[e.b]){
					dist[e.b]=dist[i]+e.c;
					if(!vist[e.b]){
						vist[e.b]=1;
						q.push(e.b);
					}
				}
			}
		}
		return dist[t]<0;
	}
	int cost=0;
	inline int DFS(int i,int f){
		if(i==t||f==0)return f;
		vis[i]=1;
		int res=0;
		for(int j=cur[i];j<(int)vv[i].size();j++){
			edge& e=ve[vv[i][j]];
			cur[i]=j;
			if(!vis[e.b]&&dist[e.b]==dist[i]+e.c&&e.f){
				ll flow=DFS(e.b,min(e.f,f));
				ve[vv[i][j]].f-=flow,ve[vv[i][j]^1].f+=flow;
				f-=flow,res+=flow;
				cost+=flow*e.c;
			}
			if(!f)break;
		}
		vis[i]=0;
		return res;
	}
	inline int MCMF(){
		int ans=0;
		while(SPFA()){
			ans+=DFS(s,1e9);
		}
		return ans;
	}
	inline void flush(){
		for(auto i:pnts)vv[i].clear();
		ve.clear();cost=0;
		pnts.clear(); 
	}
}
int n,m,a,b,c,f,p;
inline void add_edge(int x,int y,int f,int c){
	Juc::add_edg(y,x,f,c);
	Juc::add_edg(x,y,f,c);
}
using namespace FastIO;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	n=in(),m=in();
	rep(i,2,n){
		p=in();
		Juc::add_edg(i,n+1,1e9,-p);
	}
	rp(i,m){
		a=in(),b=in(),f=in(),c=in();
		add_edge(a,b,f,c);
	}
	rep(i,1,n+1)Juc::ptt(i);
	Juc::s=0,Juc::t=n+1;
	Juc::add_edg(0,1,1e9,0);
	Juc::MCMF();
	out(-Juc::cost)('\n');
	return 0;
}
//Nyn-siyn-hert
posted @ 2023-05-27 16:10  jucason_xu  阅读(10)  评论(0编辑  收藏  举报