【BZOJ3118】Orz the MST(线性规划)(对偶原理)(费用流)

本文探讨了最小生成树(MST)问题与最大费用流之间的联系,通过将MST问题转化为一个最大费用可行流问题,提供了一种新颖的求解策略。文章详细介绍了如何建立线性规划模型,通过求解其对偶问题,最终转化为二分图最大权多重匹配问题,再利用最大费用流算法解决。

传送门


不是很懂为什么现在找得到的题解全部写单纯形,明明对偶之后是一个很明显的最大费用可行流。。。

题解:

首先,由贪心可知,树边权值不会减少,非树边权值不会增加。

设树边集合为 T T T,非树边集合为 E E E

d i d_i di表示第 i i i条边的变化量。设 j  cover  i j\text{ cover }i j cover i表示一条非树边 j j j两端对应的树上路径中包含树边 i i i

由最小生成树的性质,则我们可以列出如下线性规划:
l i m i t s : d i + d j ≥ w i − w j , ∀ i ∈ T , j ∈ E , j  cover  i d i ≥ 0 m i n i m i z e : ∑ i ∈ T b i d i + ∑ i ∈ E a i d i \begin{aligned} limits:&&&&&&d_i+d_j&\geq w_i-w_j,\forall i\in T,j\in E,j\text{ cover }i\\ &&&&&&d_i&\geq0\\ minimize:&&&&&&\sum_{i\in T}b_id_i&+\sum_{i\in E}a_id_i \end{aligned} limits:minimize:di+djdiiTbidiwiwj,iT,jE,j cover i0+iEaidi

转对偶:

l i m i t s : ∑ j ∈ E , j  cover  i c i , j ≤ b i , ∀ i ∈ T ∑ i ∈ T , j  cover  i c i , j ≤ a j , ∀ j ∈ E c i , j ≥ 0 m a x m i z e : ∑ i ∈ T , j ∈ E j  cover  i ( w i − w j ) c i , j \begin{aligned} limits:&&&&&&\sum_{j\in E,j\text{ cover }i}c_{i,j}&\leq b_i,\forall i\in T\\ &&&&&&\sum_{i\in T,j\text{ cover }i}c_{i,j}&\leq a_j,\forall j\in E\\ &&&&&&c_{i,j}&\geq 0\\ maxmize:&&&&&&\sum_{\begin{aligned}i\in T,j\in E\\j\text{ cover }i\end{aligned}}(w_i&-w_j)c_{i,j} \end{aligned} limits:maxmize:jE,j cover ici,jiT,j cover ici,jci,jiT,jEj cover i(wibi,iTaj,jE0wj)ci,j

然后这个形式是一个二分图最大权多重匹配,直接建图跑最大费用可行流就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
typedef std::pair<int,int> pii;
#define fi first
#define se second

namespace NetWork{
	cs int N=1e3+7;
	int S,T,tot;
	struct edge{int to,rev,cap,w;};
	typedef std::vector<edge>::iterator iter;
	std::vector<edge> G[N];iter cur[N];
	inline void adde(int u,int v,int cap,int cost){
		G[u].push_back((edge){v,G[v].size(),cap,cost});
		G[v].push_back((edge){u,G[u].size()-1,0,-cost});
	}
	
	int dis[N],vis[N];
	inline bool SPFA(){
		memset(dis+1,-0x3f,sizeof(int)*tot);
		memset(vis+1,0,sizeof(int)*tot);
		std::queue<int> q;q.push(S);dis[S]=0;
		while(!q.empty()){
			int u=q.front();q.pop();vis[u]=false;cur[u]=G[u].begin();
			for(auto e:G[u])if(e.cap&&dis[e.to]<dis[u]+e.w){
				dis[e.to]=dis[u]+e.w;
				if(!vis[e.to])q.push(e.to),vis[e.to]=true;
			}
		}
		return dis[T]>0;
	}
	
	int tot_cost,tot_flow;
	int dfs(int u,int flow){
		if(u==T){
			tot_cost+=flow*dis[T];
			tot_flow+=flow;
			return flow;
		}
		vis[u]=true;int ans=0;
		for(iter &e=cur[u];e!=G[u].end();++e)
		if(e->cap&&!vis[e->to]&&dis[e->to]==dis[u]+e->w){
			int delta=dfs(e->to,std::min(flow-ans,e->cap));
			if(!delta){vis[e->to]=true;continue;}
			e->cap-=delta;
			G[e->to][e->rev].cap+=delta;
			if((ans+=delta)==flow)break;
		}
		vis[u]=false;return ans;
	}
	
	inline void work(){
		tot_flow=tot_cost=0;
		while(SPFA())dfs(S,0x3f3f3f3f);
		cout<<tot_cost<<"\n";
	}
}
using NetWork::S;
using NetWork::T;
using NetWork::tot;

namespace Graph{
	cs int N=3e2+7,M=1e3+7;
	int n,m,c1,c2;
	int w[M],c[M];pii E[M];
	std::vector<pii> G[N];
	
	int fa[N],fa_e[N],d[N];
	void dfs(int u,int p,int eid){
		fa[u]=p,fa_e[u]=eid;d[u]=d[p]+1;
		for(pii e:G[u])if(e.fi!=p)dfs(e.fi,u,e.se);
	}
	
	inline void work_path(int u,int v,int eid){
		while(u^v){
			if(d[u]>d[v])std::swap(u,v);
			NetWork::adde(fa_e[v],eid,0x3f3f3f3f,w[fa_e[v]]-w[eid]);
			v=fa[v];
		}
	}
	
	inline void work(){
		n=gi(),m=gi();c1=1,c2=n;S=m+1,T=tot=m+2;
		for(int re i=1;i<=m;++i){
			int u=gi(),v=gi(),val=gi(),f=gi(),a=gi(),b=gi();
			if(!f){
				E[c2]=pii(u,v);
				c[c2]=a,w[c2]=val;++c2;
			}
			else {
				E[c1]=pii(u,v);
				G[u].push_back(pii(v,c1));
				G[v].push_back(pii(u,c1));
				c[c1]=b;w[c1]=val;++c1;
			}
		}
		dfs(1,0,0);
		for(int re i=n;i<=m;++i)work_path(E[i].fi,E[i].se,i);
		for(int re i=1;i<n;++i)NetWork::adde(S,i,c[i],0);
		for(int re i=n;i<=m;++i)NetWork::adde(i,T,c[i],0);
	}
}

signed main(){
#ifdef zxyoi
	freopen("mst.in","r",stdin);
#endif
	Graph::work();
	NetWork::work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值