线性规划对偶问题 学习笔记

线性规划

线性规划是一类满足限制条件为关于自变量的线性约束,且目标函数是关于自变量的线性函数的一类最优化问题。

对于一组自变量 \(x_1,x_2,\dots,x_n\),定义线性函数 \(f(x_1,x_2,\dots,x_n)=\sum_{i=1}^{n}c_ix_i\)。不等式 \(f(x_1,x_2,\dots x_n)\le b,f(x_1,x_2,\dots x_n)\ge b\) 以及等式 \(f(x_1,x_2,\dots x_n=b\) 即被称为线性约束。

经过变换,所有线性规划都可以被写作标准型:

\[\max \sum_{i=1}^{n}c_ix_i\\ s.t.\begin{cases} \sum_{j=1}^{n}a_{i,j}x_j\le b_i&i=1,2\dots m\\ x_j\ge 0&j=1,2,\dots n \end{cases} \]

具体的,\(\ge\) 的线性约束可以将系数取反后变为 \(\le\)\(=\) 可以拆为一个 \(\ge\)\(\le\)。如果对变量 \(x_i\) 没有 \(\ge 0\) 的约束,可以加入新变量 \(x_1,x_2\) 满足 \(x_i=x_1-x_2\),令$ x_1,x_2\ge 0$,与原问题等价。

这个式子可以用矩阵表示:

\[\max c^Tx\\ Ax\le b\\ x\ge 0 \]

其中定义向量 \(x\le y\) 当且仅当 \(x\) 的每一维都 \(\le y\)

举几个例子:

  • 最大流问题转化为线性规划:

    定义 \(f_{u,v}\) 为边 \((u,v)\) 的流量,\(c_{u,v}\) 为边 \((u,v)\) 的容量,对于源汇点的问题,从汇点向源点连接一条流量为 \(\inf\) 的边,使得每个点都满足流量守恒,记 \(V,E\) 分别为点集、边集。则最大流问题相当于:

    \[\max f_{t,s}\\ s.t.\begin{cases} f_{u,v}\le c_{u,v}&(u,v)\in E\\ \sum_v f_{u,v}=\sum_v f_{v,u}&u\in V\\ f_{u,v}\ge 0&(u,v)\in E\cup\{(t,s)\} \end{cases} \]

  • 最小费用流问题:即在满足流量守恒的条件下使费用最小,不需要满足流量最大的限制,记 \(w_{u,v}\) 为边 \((u,v)\) 的费用。

    \[\max \sum_{(u,v)\in E}f_{u,v}w_{u,v}\\ s.t.\begin{cases} f_{u,v}\le c_{u,v}&(u,v)\in E\\ \sum_v f_{u,v}=\sum_v f_{v,u}&u\in V/\{s,t\}\\ f_{u,v}\ge 0&(u,v)\in E \end{cases} \]

对偶问题

形式化定义:对于标准型线性规划

\[\max \sum_{i=1}^{n}c_ix_i\\ s.t.\begin{cases} \sum_{j=1}^{n}a_{i,j}x_j\le b_i&i=1,2\dots m\\ x_j\ge 0&j=1,2,\dots n \end{cases} \]

定义它的对偶线性规划为:

\[\min \sum_{i=1}^{m}b_iy_i\\ s.t.\begin{cases} \sum_{i=1}^{m}a_{i,j}y_i\ge c_j&j=1,2\dots n\\ y_j\ge 0&i=1,2,\dots m \end{cases} \]

其中 \(y\) 称为对偶变量。可以看出者相当于将每条线性约束变换为了一个对偶变量,目标线性函数则变成了约束。

写成矩阵的形式:

\[\max c^Tx\\ Ax\le b\\ x\ge 0 \]

对偶后:

\[\min b^Ty\\ A^Ty\ge c\\ y\ge 0 \]

  • 弱对偶定理:

    即对于上述两个线性规划,有 \(c^T x\le b^T y\)

    考虑由于 \(y\) 满足对偶线性规划,故 \(\sum_{i=1}^{m}a_{i,j}y_i\ge c_j\)

    \[\sum_{j=1}^{n}c_jx_j\le \sum_{j=1}^{n}(\sum_{i=1}^{m}a_{i,j})x_jy_i=\sum_{i=1}^{m}(\sum_{j=1}^{n}a_{i,j}x_j)y_i\le \sum_{i=1}^{m}b_iy_i \]

  • 强对偶定理:

    即对于上述两个线性规划,有 \(c^T x= b^T y\)

    证明较为复杂,在此不多赘述。

由此可以看出,线性规划与其对偶问题的最优解相同,可以求其对偶问题的最优解得到原问题的最优解。

经典问题的对偶

  • 最大流问题:原线性规划:

    \[\max f_{t,s}\\ s.t.\begin{cases} f_{u,v}\le c_{u,v}&(u,v)\in E\\ \sum_v f_{u,v}-\sum_v f_{v,u}=0&u\in V\\ f_{u,v}\ge 0&(u,v)\in E\cup\{(t,s)\} \end{cases} \]

    这个线性规划有两种线性约束。记 \(f_{u,v}\le c_{u,v}\) 的对偶变量为 \(d_{u,v}\)\(\sum_v f_{u,v}-\sum_v f_{v,u}=0\) 产生的对偶变量为 \(p_u\)

    故对偶后,目标函数中 \(p_u\) 的系数抵消为 \(0\),对偶问题即为:

    \[\min \sum_{(u,v)\in E}c_{u,v}d_{u,v}\\ s.t.\begin{cases} d_{u,v}-p_u+p_v\ge 0&(u,v)\in E\\ p_s-p_t\ge 1\\ p_u\ge 0&u \in V\\ d_{u,v}\ge 0&(u,v)\in E \end{cases} \]

    考虑最小割问题:设一个割将所有点分为两个不相交集合 \(S,T\),令 \(p_u=[u\in S]\)\(d_{u,v}\) 表示边 \((u,v)\) 是否被割去,写出线性规划为:

    \[\min \sum_{(u,v)\in E}c_{u,v}d_{u,v}\\ s.t.\begin{cases} d_{u,v}-p_u+p_v\ge 0&(u,v)\in E\\ p_s-p_t\ge 1\\ p_u\in[0,1]&u \in V\\ d_{u,v}\in[0,1]&(u,v)\in E \end{cases} \]

    可以看出与最大流对偶问题的唯一区别在于限制条件 \(p_u,d_{u,v}\in[0,1]\)。不过可以证明一定存在一组 \(\in[0,1]\) 的最优解满足这一限制,因此我们就得到了经典结论:最大流等于最小割。

  • 最小费用流问题:为了应对更广泛的问题,我们设 \(b_u\) 表示点 \(u\) 要求流出流量减流入流量 \(=b_u\),故:

    \[\min \sum_{(u,v)\in E}f_{u,v}w_{u,v}\\ s.t.\begin{cases} -f_{u,v}\ge -c_{u,v}&(u,v)\in E\\ \sum_v f_{v,u}-\sum_v f_{u,v}=-b_u&u\in V\\ f_{u,v}\ge 0&(u,v)\in E \end{cases} \]

    \(z_{u,v}\)\(f_{u,v}\le c_{u,v}\) 的对偶变量,\(p_u\)\(\sum_v f_{u,v}-\sum_v f_{v,u}=b_u\) 的对偶变量,则对偶问题为:

    \[\max \sum_u-b_up_u-\sum_{(u,v)\in E}z_{u,v}c_{u,v}\\ s.t.\begin{cases} p_v-p_u-z_{u,v}\le w_{u,v}&(u,v)\in E\\ z_{u,v}\ge 0&(u,v)\in E\\ p_u\ge 0&u\in V \end{cases} \]

    将所有 \(p_v-p_u-z_{u,v}\le w_{u,v}\) 的限制加起来,并将所求函数取负后相加,于是答案就是下面这个式子的相反数:

    \[\min \sum_ub_up_u+\sum_{(u,v)\in E}c_{u,v}\max(p_v-p_u-w_{u,v},0) \]

    因此形如这样式子的问题,可以将其转化为最小费用流问题求解。

    还有一件事:对于下面这个问题,我们一般还会要求 \(p\) 均为整数。不过可以用调整法证明,如果所有 \(w_{u,v}\) 均为整数,一定可以在所有 \(p_u\) 均为整数时取到最优解。

例题

  • [ZJOI2013] 防守战线

    对于题目中的区间限制 \((l,r,d)\),可以想到记前缀和数组 \(p\),那么该限制相当于 \(p_r-p_{l-1}-d\ge 0\),同时也要满足 \(p_i-p_{i-1}\ge 0\),最终的费用就是 \(\sum_i (c_i-c_{i+1})p_i\)

    这几个限制都可以通过乘上 \(+\infty\) 的的系数来体现,于是相当于要求:

    \[\min \sum_u(c_u-c_{u+1})p_u+\left(\sum_{(l,r,d)}+\infty\max(p_{l-1}+d-p_r,0)\right)+\left(\sum_{i}+\infty\max(p_{i-1}-p_i,0)\right) \]

    的相反数。

    这就是上面最小费用流的柿子。于是对于 \(u\in[0,n]\),如果 \(c_u-c_{u+1}>0\) 就从源点向 \(u\) 连边 \((c_u-c_{u+1},0)\),否则从 \(u\) 向汇点连边 \((c_{u+1}-c_u,0)\)。从 \(u\)\(u-1\) 连边 \((+\infty,0)\),从 \(r\)\(l-1\) 连边 \((+\infty,-d)\),跑出最小费用最大流的相反数即为答案。

    直接跑 \(SPFA\)\(TLE\),需要用 \(johnson\) 算法先转化为正权边,再跑 \(dijkstra\)

    #include<bits/stdc++.h>
    using namespace std;
    const int M=5e4+10,N=5e3+10,inf=0x3f3f3f3f;
    namespace mcmf{
    	int tot,s,t,first[N],cnt=1,dis[N],vis[N],cur[N],h[N],rec[N];
    	struct node{
    		int u,v,f,c,nxt;
    	}e[M*2];
    	inline void add(int u,int v,int f,int c){
    		e[++cnt].u=u;e[cnt].v=v;e[cnt].f=f;e[cnt].c=c;
    		e[cnt].nxt=first[u];
    		first[u]=cnt;
    	}
    	inline void Add(int u,int v,int f,int c){
    		add(u,v,f,c);
    		add(v,u,0,-c);
    	}
    	inline bool SPFA(){
    		queue<int>q;
    		memset(dis+1,0x3f,sizeof(int)*(tot));
    		dis[s]=0;vis[s]=1;
    		q.push(s);
    		while(!q.empty()){
    			int u=q.front();q.pop();vis[u]=0;
    			for(int i=first[u];i;i=e[i].nxt){
    				int v=e[i].v;
    				if(e[i].f>0&&dis[v]>dis[u]+e[i].c){
    					dis[v]=dis[u]+e[i].c;
    					if(!vis[v]){vis[v]=1;q.push(v);}
    				}
    			}
    		}
    		for(int i=1;i<=tot;++i) rec[i]=dis[i];
    		return dis[t]<inf;
    	}
    	inline bool dijkstra(){
    		for(int i=1;i<=tot;++i) h[i]+=rec[i];
    		priority_queue<pair<int,int> >q;
    		memset(dis+1,0x3f,sizeof(int)*(tot));
    		memset(vis+1,0,sizeof(int)*(tot));
    		dis[s]=0;
    		q.push(make_pair(0,s));
    		while(!q.empty()){
    			int u=q.top().second;q.pop();
    			if(vis[u]) continue;vis[u]=1;
    			for(int i=first[u];i;i=e[i].nxt){
    				int v=e[i].v,tmp=h[u]+e[i].c-h[v];
    				if(e[i].f>0&&dis[v]>dis[u]+tmp){
    					dis[v]=dis[u]+tmp;
    					q.push(make_pair(-dis[v],v));
    				}
    			}
    		}
    		for(int i=1;i<=tot;++i) rec[i]=dis[i];
    		return dis[t]<inf;
    	}
    	inline int dfs(int u,int f){
    		if(u==t||!f) return f;
    		int used=0;vis[u]=1;
    		for(int& i=cur[u];i;i=e[i].nxt){
    			int v=e[i].v;
    			if(vis[v]||dis[v]!=dis[u]+(h[u]+e[i].c-h[v])) continue;
    			int fl=dfs(v,min(f,e[i].f));
    			e[i].f-=fl;e[i^1].f+=fl;
    			used+=fl;f-=fl;
    			if(!f) break;
    		}
    		if(f) dis[u]=0x3f3f3f3f;
    		return vis[u]=0,used;
    	}
    	inline int dinic(int S,int T){
    		s=S;t=T;
    		int mxf=0,mxc=0;
    		for(SPFA();dijkstra();){
    			memset(vis+1,0,sizeof(int)*(tot));
    			memcpy(cur,first,sizeof(int)*(tot));
    			int flow=dfs(s,inf);
    			mxf+=flow;mxc+=(dis[t]-h[s]+h[t])*flow;
    		}
    		return mxc;
    	}
    }
    using mcmf::tot;
    using mcmf::Add;
    int n,m,c[N],d[N];
    int main(){
    	scanf("%d%d",&n,&m);
    	tot=n+1;
    	int s=++tot,t=++tot;
    	for(int i=1;i<=n;++i)
    		scanf("%d",&c[i]);
    	for(int i=0;i<=n;++i){
    		if(c[i]>c[i+1]) Add(s,i+1,c[i]-c[i+1],0);
    		else Add(i+1,t,c[i+1]-c[i],0);
    		if(i>0) Add(i+1,i,inf,0);
    	}
    	for(int i=1,l,r,d;i<=m;++i){
    		scanf("%d%d%d",&l,&r,&d);
    		Add(r+1,l,inf,-d);
    	
    	printf("%d\n",-mcmf::dinic(s,t));
    	return 0;
    }
    
  • AGC043F

    首先排序使得 \(S_{i,1}\le S_{i,2}\le \dots \le S_{i,k_i}\)

    \(x_{i,j}\) 表示第 \(i\) 家珠宝店的前 \(j-1\) 种珠宝被选择的次数之和,那么有:

    • \(0=x_{i,1}\le x_{i,2}\le x_{i,3}\le \dots \le x_{i,k_i+1}=A\)
    • \(x_{i,j+1}-x_{i,j}\le c_{i,j}\)
    • 对于限制 \((u_i,v_i,w_i)\),对于 \(\forall j\in[1,k_{v_i}]\),记 \(t\) 为最小的 \(t\) 使得 \(S_{u_i,t}+w_i\ge S_{v_i,j}\),那么如果在珠宝盒 \(v_i\) 选择 \((v_i,j\sim k_{v_i})\)\(u_i\) 珠宝盒只能选择 \(t\sim k_{u_i}\),因此有 \(x_{u_i,t}\le x_{v_i,j}\)

    可以发现以上就是原问题的充要条件,最终要求最小化 \(\sum_{i,j}p_{i,j}(x_{i,j+1}-x_{i,j})\)

    与上一个问题基本相同,可以写作以下形式:

    \[\min \sum_{i,j}p_{i,j}(x_{i,j+1}-x_{i,j})+\sum_{i,j}\left(+\infty(\max(x_{i,j}-x_{i,j+1},0+\max(x_{i,j+1}-x_{i,j}-c_{i,j},0)\right)+\sum_i\infty x_{i,1}\\ +\sum_i\infty\left(\max(x_{i,k_i+1}-A,0)+\max(A-x_{i,k_i+1},0)\right)+\sum_{u,v,w,j}\infty\max(x_{u,w}-x_{v,j},0) \]

    对偶后变成最小费用流,对 \((i,1)\) 的出度减入度要求为 \(+\infty\),其余点要求流量平衡。前 \(2\)\(\sum\) 以及最后一个 \(\sum\) 都正常连边即可,第 \(4\)\(\sum\) 可以看作 \(x_{i,k_i+1}-x_{i,1}-A\),于是相当于从 \((i,1)\)\((i,k_i+1)\) 连流量为 \(+\infty\),费用为 \(A\) 的边;从 \((i,k_i+1)\)\((i,1)\) 连流量为 \(+\infty\),费用为 \(-A\) 的边。因此可以将 \((i,1)\) 作为源点,\((i,k_i+1)\) 向汇点连费用为 \(-A\) 的边,就变成一个最小费用流问题了。

    对于多组询问,考虑最终的费用中,\(A\) 只在 \((i,k_i+1)\) 连向汇点的边上作为费用,并且所有连向汇点的边的费用均为 \(-A\),因此最终答案一定形如 \(A\times flow-C\) 的形式,其中 \(flow\) 为流量,\(C\) 为此时其他边的费用。注意到最小费用流的过程形如不断跑最短路,跑出 \(A=0\) 时的 \(dis_t\),如果 \(dis_t\ge A\) 那么就终止,否则将本次增广的流加入答案。因此可以预处理出 \(A=0\) 时每次跑出的 \(dis_t\),以及此时的流量及费用,那么对于每组询问可以通过二分找到对应的 \(flow\) 为多少,即可回答询问。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int T=40,M=5e4+10,N=5e3+10,inf=1e9;
    ll Inf;
    namespace mcmf{
    	ll dis[N];
    	int tot,s,t,first[N],cnt=1,vis[N],cur[N];
    	struct node{
    		int u,v,f,nxt;ll c;
    	}e[M*2];
    	inline void add(int u,int v,int f,ll c){
    		e[++cnt].u=u;e[cnt].v=v;e[cnt].f=f;e[cnt].c=c;
    		e[cnt].nxt=first[u];
    		first[u]=cnt;
    	}
    	inline void Add(int u,int v,int f,ll c){
    		add(u,v,f,c);
    		add(v,u,0,-c);
    	}
    	inline bool SPFA(){
    		queue<int>q;
    		memset(dis+1,0x3f,sizeof(ll)*(tot));
    		if(!Inf) Inf=dis[1]; 
    		dis[s]=0;vis[s]=1;
    		q.push(s);
    		while(!q.empty()){
    			int u=q.front();q.pop();vis[u]=0;
    			for(int i=first[u];i;i=e[i].nxt){
    				int v=e[i].v;
    				if(e[i].f>0&&dis[v]>dis[u]+e[i].c){
    					dis[v]=dis[u]+e[i].c;
    					if(!vis[v]){vis[v]=1;q.push(v);}
    				}
    			}
    		}
    		return dis[t]<Inf;
    	}
    	inline int dfs(int u,int f){
    		if(u==t||!f) return f;
    		int used=0;vis[u]=1;
    		for(int& i=cur[u];i;i=e[i].nxt){
    			int v=e[i].v; 
    			if(!e[i].f||vis[v]||dis[v]!=dis[u]+e[i].c) continue;
    			int fl=dfs(v,min(f,e[i].f));
    			e[i].f-=fl;e[i^1].f+=fl;
    			used+=fl;f-=fl;
    			if(!f) break;
    		}
    		if(f) dis[u]=Inf;
    		return vis[u]=0,used;
    	}
    }
    using namespace mcmf;
    struct jew{
    	int s,p;ll c;
    }a[T][T]; 
    struct pt{
    	ll k,c,f;
    };
    inline bool operator <(const pt &x,const pt &y){
    	return x.k<y.k;
    }
    vector<pt> vec;
    int n,k[T],pos[T][T];
    
    int main(){
    	scanf("%d",&n);
    	s=++tot,t=++tot;
    	for(int i=1;i<=n;++i){
    		scanf("%d",&k[i]);
    		for(int j=1;j<=k[i];++j)
    			scanf("%d%d%lld",&a[i][j].s,&a[i][j].p,&a[i][j].c);
    		sort(a[i]+1,a[i]+k[i]+1,[&](const jew &x,const jew &y){
    			return x.s<y.s;	
    		});
    		pos[i][1]=s;
    		for(int j=2;j<=k[i]+1;++j){
    			pos[i][j]=++tot;
    			Add(pos[i][j-1],pos[i][j],a[i][j-1].p,0);
    			Add(pos[i][j-1],pos[i][j],inf,a[i][j-1].c);
    			Add(pos[i][j],pos[i][j-1],inf,0);
    		}
    		Add(pos[i][k[i]+1],t,inf,0);
    	}
    	int m;scanf("%d",&m);
    	for(int i=1,u,v,w;i<=m;++i){
    		scanf("%d%d%d",&u,&v,&w);
    		int t=1;
    		for(int j=1;j<=k[v];++j){
    			while(t<=k[u]&&a[u][t].s+w<a[v][j].s) ++t;
    			Add(pos[v][j],pos[u][t],inf,0);
    		}
    	}
    	ll mxf=0;ll mxc=0;
    	while(SPFA()){
    		memset(vis+1,0,sizeof(int)*(tot+1));
    		memcpy(cur,first,sizeof(int)*(tot+1));
    		vec.push_back((pt){dis[t],mxc,mxf});
    		int flow=dfs(s,inf);
    		if(flow>inf/2) break;
    		mxf+=flow;mxc+=dis[t]*flow;
    	}
    	int q;scanf("%d",&q);
    	while(q--){
    		ll a;scanf("%lld",&a);
    		auto t=lower_bound(vec.begin(),vec.end(),(pt){a,0,0});
    		if(t==vec.end()) puts("-1");
    		else printf("%lld\n",(*t).f*a-(*t).c);
    	}
    	
    	return 0;
    }
    
posted @ 2022-04-03 22:31  cjTQX  阅读(744)  评论(0编辑  收藏  举报