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

线性规划

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

对于一组自变量 x1,x2,,xn,定义线性函数 f(x1,x2,,xn)=i=1ncixi。不等式 f(x1,x2,xn)b,f(x1,x2,xn)b 以及等式 f(x1,x2,xn=b 即被称为线性约束。

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

maxi=1ncixis.t.{j=1nai,jxjbii=1,2mxj0j=1,2,n

具体的, 的线性约束可以将系数取反后变为 = 可以拆为一个 。如果对变量 xi 没有 0 的约束,可以加入新变量 x1,x2 满足 xi=x1x2,令x1,x20,与原问题等价。

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

maxcTxAxbx0

其中定义向量 xy 当且仅当 x 的每一维都 y

举几个例子:

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

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

    maxft,ss.t.{fu,vcu,v(u,v)Evfu,v=vfv,uuVfu,v0(u,v)E{(t,s)}

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

    max(u,v)Efu,vwu,vs.t.{fu,vcu,v(u,v)Evfu,v=vfv,uuV/{s,t}fu,v0(u,v)E

对偶问题

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

maxi=1ncixis.t.{j=1nai,jxjbii=1,2mxj0j=1,2,n

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

mini=1mbiyis.t.{i=1mai,jyicjj=1,2nyj0i=1,2,m

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

写成矩阵的形式:

maxcTxAxbx0

对偶后:

minbTyATycy0

  • 弱对偶定理:

    即对于上述两个线性规划,有 cTxbTy

    考虑由于 y 满足对偶线性规划,故 i=1mai,jyicj

    j=1ncjxjj=1n(i=1mai,j)xjyi=i=1m(j=1nai,jxj)yii=1mbiyi

  • 强对偶定理:

    即对于上述两个线性规划,有 cTx=bTy

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

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

经典问题的对偶

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

    maxft,ss.t.{fu,vcu,v(u,v)Evfu,vvfv,u=0uVfu,v0(u,v)E{(t,s)}

    这个线性规划有两种线性约束。记 fu,vcu,v 的对偶变量为 du,vvfu,vvfv,u=0 产生的对偶变量为 pu

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

    min(u,v)Ecu,vdu,vs.t.{du,vpu+pv0(u,v)Epspt1pu0uVdu,v0(u,v)E

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

    min(u,v)Ecu,vdu,vs.t.{du,vpu+pv0(u,v)Epspt1pu[0,1]uVdu,v[0,1](u,v)E

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

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

    min(u,v)Efu,vwu,vs.t.{fu,vcu,v(u,v)Evfv,uvfu,v=buuVfu,v0(u,v)E

    zu,vfu,vcu,v 的对偶变量,puvfu,vvfv,u=bu 的对偶变量,则对偶问题为:

    maxubupu(u,v)Ezu,vcu,vs.t.{pvpuzu,vwu,v(u,v)Ezu,v0(u,v)Epu0uV

    将所有 pvpuzu,vwu,v 的限制加起来,并将所求函数取负后相加,于是答案就是下面这个式子的相反数:

    minubupu+(u,v)Ecu,vmax(pvpuwu,v,0)

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

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

例题

  • [ZJOI2013] 防守战线

    对于题目中的区间限制 (l,r,d),可以想到记前缀和数组 p,那么该限制相当于 prpl1d0,同时也要满足 pipi10,最终的费用就是 i(cici+1)pi

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

    minu(cucu+1)pu+((l,r,d)+max(pl1+dpr,0))+(i+max(pi1pi,0))

    的相反数。

    这就是上面最小费用流的柿子。于是对于 u[0,n],如果 cucu+1>0 就从源点向 u 连边 (cucu+1,0),否则从 u 向汇点连边 (cu+1cu,0)。从 uu1 连边 (+,0),从 rl1 连边 (+,d),跑出最小费用最大流的相反数即为答案。

    直接跑 SPFATLE,需要用 johnson 算法先转化为正权边,再跑 dijkstra

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    #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

    首先排序使得 Si,1Si,2Si,ki

    xi,j 表示第 i 家珠宝店的前 j1 种珠宝被选择的次数之和,那么有:

    • 0=xi,1xi,2xi,3xi,ki+1=A
    • xi,j+1xi,jci,j
    • 对于限制 (ui,vi,wi),对于 j[1,kvi],记 t 为最小的 t 使得 Sui,t+wiSvi,j,那么如果在珠宝盒 vi 选择 (vi,jkvi)ui 珠宝盒只能选择 tkui,因此有 xui,txvi,j

    可以发现以上就是原问题的充要条件,最终要求最小化 i,jpi,j(xi,j+1xi,j)

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

    mini,jpi,j(xi,j+1xi,j)+i,j(+(max(xi,jxi,j+1,0+max(xi,j+1xi,jci,j,0))+ixi,1+i(max(xi,ki+1A,0)+max(Axi,ki+1,0))+u,v,w,jmax(xu,wxv,j,0)

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

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

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    #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 @   cjTQX  阅读(928)  评论(0编辑  收藏  举报
历史上的今天:
2021-04-03 洛谷P5591 小猪佩奇学数学【单位根反演】
2021-04-03 【UOJ #214】合唱队形【min-max容斥】【DP】
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开