线性规划和网络流的单纯形法

线性规划

线性规划问题

\[\max \sum _{i=1}^nc_jx_j\\ \text{s.t. :}\\ \sum _{t=1}^n a_{it}x_t\le b_i,i\in \Z\cap [1,m_1]\\ \sum _{t=1}^n a_{it}x_t=b_i,i\in \Z\cap (m_1,m_1+m_2]\\ \sum_{t=1}^n a_{it}x_t\ge b_i,i\in (m_1+m_2,m_1+m_2+m_3]\\ x_i\ge 0,\forall i \]

网络流是线性规划的特殊形式。

\(m=m_1+m_2+m_3\) 个的前三约束中至少有 \(n\) 个约束以等号满足的可行解称为基本可行解

线性规划基本定理:若线性规划存在最优解,则存在基本可行最优解。

上述定理的重要意义在于,它把一个最优化问题转化为一个组合问题。

若一个线性规划只有等号约束,称其具有标准形式。如果在每个等式中,至少有一个变量的系数为正,且变量只在此出现。这样的线性规划问题叫做约束标准型线性规划问题。

每个方程中找到一个这样的变量,称为基本变量,剩下的是非基本变量。

单纯形法

这样,问题被分成了两个部分:把线性规划转为约束标准型,求解约束标准型线性规划。

先解决约束标准型线性规划。

思路:求出一组解,经过调整(转轴)得到最优解。

先置所有非基本变量为 \(0\),求出基本变量的解。

有所谓单纯形表,自行体会,,,

image

第一步:选出其增加也使目标函数增加的,下标最小的非基本变量作为入基变量。找不到则目前就是最优解。此处 \(x_3\) 对应系数是正数,符合条件。\(x_3\) 是入基变量。

第二步:考察限制入基变量的变量中最要紧的限制,让其离基。

如果入基变量所在列的所有元素都是负值,则目标函数无界,已经得到了问题的无界解。

受限的增加量可以用正的入基变量所在列的元素(称为主元素)去除主元素所在行的“常数列”(最左边的列)中元素而得到。所得到数值越小说明受到限制越要紧。

例如此处 \(x_3\) 入基,即要求找到最小的常数除以正对应列值。如果此最大值是负数,即没有限制,那么解无界。比方说,这里 \(x_1\) 主元素是负值不予考虑,\(x_4\) 对应 \(12/4=3\)\(x_6\) 对应 \(10/3\)\(3\) 是最小的,取 \(x_4\) 离基。

第三步:转轴变换。目的是执行入基、离基过程并修正单纯形表。

image

这就是转轴变换后的单纯形表。

对于离基变量的那个方程表示入基变量:

\[x_3=3-\frac 12 x_2+\frac 14x_4 \]

在其他行和目标函数消去 \(x_3\)\(x_3\) 的位置变为 \(x_4\)

第四步:返回第一步,递归知道得到无界或最优解。

转化方法

看起来,一般线性规划比约束标准型线性规划困难许多,但是我们现在声称这是等难的。

首先把不等式转为等式。每个不等式引入不影响答案的松弛变量即可:例如:

\[x_1+x_2\ge 3\rightarrow x_1+x_2-y_1=3 \]

引入"人工基" \(z_i\),把第 \(i\) 个等式约束加上 \(z_i\)

采用大 \(M\) 法,即把答案变量的 \(z_i\) 系数设为 \(-\infty\),来强制其在答案中为 \(0\)

例题&代码 P3980

这里使用了对偶变换(也可以不)。

主要内容可见 link

// Problem: P3980 [NOI2008] 志愿者招募
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3980
// Memory Limit: 125 MB
// Time Limit: 2000 ms
// UOB Koala'
// 
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5,maxm=1e4+5,INF=1e9;
int n,m;
int id[maxn+maxm];
#define db double 
const db eps=1e-8;
db mp[maxm][maxn],b[maxm],*c=mp[0],ans[maxn+maxm];
//mp 是单纯形表
void pivot(int r,int c){//转轴
	db coe=1/mp[r][c];
	swap(id[n+r],id[c]);
	mp[r][c]=1.0;
	for(int i=1;i<=n;i++)mp[r][i]*=coe;
	b[r]*=coe;
	for(int i=0;i<=m;i++){
		if(i==r)continue;
		coe=mp[i][c];mp[i][c]=0.0;
		for(int j=1;j<=n;j++)mp[i][j]-=coe*mp[r][j];
		b[i]-=coe*b[r];
	}
}
bool simplex(){
	while(1){
		int A=0,B=0;
		db mn=INF;
		for(int i=1;i<=n;i++)if(c[i]>c[B])B=i;//入基
		if(!B)return 1;//找到解
		for(int i=1;i<=m;i++){
			if(mp[i][B]>eps&&b[i]/mp[i][B]<mn){
				mn=b[i]/mp[i][B],A=i;
			}
		}
		if(!A)return 0;//无界
		// cout<<A<<" "<<B<<endl;
		pivot(A,B);//A出基 B入基
	}
}
signed main(){
	// ios::sync_with_stdio(0);
	// cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>c[i];
	for(int i=1;i<=m;i++){
		int l,r;cin>>l>>r>>b[i];
		for(int j=l;j<=r;j++)mp[i][j]=1;
	}
	if(simplex())cout<<(int(-b[0]))<<endl;
	return 0;
}

网络流单纯形

大概步骤是:

1.找到残量网络上的生成树(支撑树)

2.枚举边,判断支撑树加上这条边是否构成费用权的负环。不是,就枚举下一条边;否则进入 3。

3.将这条边入基,找到构成的负环上剩余流量最小的边出基。在这个过程中,将这个边推流,即把负环上所有边的流量加上这个值(最小的残量)。

4.继续枚举下一条边。重复 2~4 直到不可执行。

另外,为了满足线性规划的约束标准型,需要先从 \(T\)\(S\) 连容量为 \(\infty\),费用 \(-\infty\) 的边。

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace MCMF{
	const int maxn=1e5+5,INF=1e9;
	struct edge{
		int u,v,f,w;
	};
	vector<edge> G;
	void init(){
		G.push_back({0,0,0,0});
		G.push_back({0,0,0,0});
	}
	vector<int> E[maxn];
	void add_edge(int u,int v,int f,int w){
		G.push_back({u,v,f,w});
		G.push_back({v,u,0,-w});
		E[u].push_back(G.size()-2);
		E[v].push_back(G.size()-1);
	}
	int pre[maxn],fa[maxn],fe[maxn],cir[maxn],tag[maxn];
	int now=0,S,T;
	void init_zct(int x,int e,int nod=1){
		fe[x]=e,fa[x]=G[e].u,tag[x]=nod;
		for(auto g:E[x]){
			if(tag[G[g].v]!=nod&&G[g].f)init_zct(G[g].v,g,nod);
		}
	}
	int sum(int x){
		if(tag[x]==now)return pre[x];
		tag[x]=now,pre[x]=sum(fa[x])+G[fe[x]].w;
		return pre[x];
	}
	int push_flow(int x){
		int rt=G[x].u,lca=G[x].v,cnt=0,del=0,P=2;
		++now;
		while(rt)tag[rt]=now,rt=fa[rt];
		while(tag[lca]!=now)tag[lca]=now,lca=fa[lca];
		int F=G[x].f,cst=0;
		for(int u=G[x].u;u!=lca;u=fa[u]){
			cir[++cnt]=fe[u];
			if(F>G[fe[u]].f)F=G[fe[u]].f,del=u,P=0;
		}
		for(int u=G[x].v;u!=lca;u=fa[u]){
			cir[++cnt]=fe[u]^1;
			if(F>G[fe[u]^1].f)F=G[fe[u]^1].f,del=u,P=1;
		}
		cir[++cnt]=x;
		for(int i=1;i<=cnt;i++)cst+=F*G[cir[i]].w,G[cir[i]].f-=F,G[cir[i]^1].f+=F;
		if(P==2)return cst;
		int u=G[x].u,v=G[x].v;
		if(P==1)swap(u,v);
		int lste=x^P,lstu=v,tmp;
		while(lstu!=del){
			lste^=1,--tag[u];
			swap(fe[u],lste);
			tmp=fa[u],fa[u]=lstu,lstu=u,u=tmp;
		}
		return cst;
	}
	int minc=0;
	int simplex(){
		add_edge(T,S,INF,-INF);
		init_zct(T,0,++now);
		tag[T]=++now,fa[T]=0;
		bool run=1;
		while(run){
			run=0;
			for(int i=0;i<G.size();i++){
				if(G[i].f&&G[i].w+sum(G[i].u)-sum(G[i].v)<0)minc+=push_flow(i),run=1;
			}
		}
		minc+=G[G.size()-1].f*INF;
		return G[G.size()-1].f;
	}
}
using MCMF::add_edge;
using MCMF::simplex;
using MCMF::minc;
using MCMF::S;
using MCMF::T;
int n,m;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;S=1,T=n;
	MCMF::init();//Important!!1
	for(int i=1;i<=m;i++){
		int u,v,a,b;cin>>u>>v>>a>>b;
		add_edge(u,v,a,b);
	}
	int f=simplex();
	cout<<f<<" "<<minc<<endl;
	return 0;
}
posted @ 2024-02-07 20:07  British_Union  阅读(81)  评论(3编辑  收藏  举报