线性规划对偶问题 学习笔记
线性规划
线性规划是一类满足限制条件为关于自变量的线性约束,且目标函数是关于自变量的线性函数的一类最优化问题。
对于一组自变量 ,定义线性函数 。不等式 以及等式 即被称为线性约束。
经过变换,所有线性规划都可以被写作标准型:
具体的, 的线性约束可以将系数取反后变为 , 可以拆为一个 和 。如果对变量 没有 的约束,可以加入新变量 满足 ,令,与原问题等价。
这个式子可以用矩阵表示:
其中定义向量 当且仅当 的每一维都 。
举几个例子:
-
最大流问题转化为线性规划:
定义 为边 的流量, 为边 的容量,对于源汇点的问题,从汇点向源点连接一条流量为 的边,使得每个点都满足流量守恒,记 分别为点集、边集。则最大流问题相当于:
-
最小费用流问题:即在满足流量守恒的条件下使费用最小,不需要满足流量最大的限制,记 为边 的费用。
对偶问题
形式化定义:对于标准型线性规划
定义它的对偶线性规划为:
其中 称为对偶变量。可以看出者相当于将每条线性约束变换为了一个对偶变量,目标线性函数则变成了约束。
写成矩阵的形式:
对偶后:
-
弱对偶定理:
即对于上述两个线性规划,有 。
考虑由于 满足对偶线性规划,故 。
故
-
强对偶定理:
即对于上述两个线性规划,有 。
证明较为复杂,在此不多赘述。
由此可以看出,线性规划与其对偶问题的最优解相同,可以求其对偶问题的最优解得到原问题的最优解。
经典问题的对偶
-
最大流问题:原线性规划:
这个线性规划有两种线性约束。记 的对偶变量为 , 产生的对偶变量为 。
故对偶后,目标函数中 的系数抵消为 ,对偶问题即为:
考虑最小割问题:设一个割将所有点分为两个不相交集合 ,令 , 表示边 是否被割去,写出线性规划为:
可以看出与最大流对偶问题的唯一区别在于限制条件 。不过可以证明一定存在一组 的最优解满足这一限制,因此我们就得到了经典结论:最大流等于最小割。
-
最小费用流问题:为了应对更广泛的问题,我们设 表示点 要求流出流量减流入流量 ,故:
令 为 的对偶变量, 为 的对偶变量,则对偶问题为:
将所有 的限制加起来,并将所求函数取负后相加,于是答案就是下面这个式子的相反数:
因此形如这样式子的问题,可以将其转化为最小费用流问题求解。
还有一件事:对于下面这个问题,我们一般还会要求 均为整数。不过可以用调整法证明,如果所有 均为整数,一定可以在所有 均为整数时取到最优解。
例题
-
对于题目中的区间限制 ,可以想到记前缀和数组 ,那么该限制相当于 ,同时也要满足 ,最终的费用就是 。
这几个限制都可以通过乘上 的的系数来体现,于是相当于要求:
的相反数。
这就是上面最小费用流的柿子。于是对于 ,如果 就从源点向 连边 ,否则从 向汇点连边 。从 向 连边 ,从 向 连边 ,跑出最小费用最大流的相反数即为答案。
直接跑 会 ,需要用 算法先转化为正权边,再跑 。
复制代码- 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
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; }
-
首先排序使得 。
记 表示第 家珠宝店的前 种珠宝被选择的次数之和,那么有:
- 。
- 对于限制 ,对于 ,记 为最小的 使得 ,那么如果在珠宝盒 选择 , 珠宝盒只能选择 ,因此有 。
可以发现以上就是原问题的充要条件,最终要求最小化 ,
与上一个问题基本相同,可以写作以下形式:
对偶后变成最小费用流,对 的出度减入度要求为 ,其余点要求流量平衡。前 个 以及最后一个 都正常连边即可,第 个 可以看作 ,于是相当于从 向 连流量为 ,费用为 的边;从 向 连流量为 ,费用为 的边。因此可以将 作为源点, 向汇点连费用为 的边,就变成一个最小费用流问题了。
对于多组询问,考虑最终的费用中, 只在 连向汇点的边上作为费用,并且所有连向汇点的边的费用均为 ,因此最终答案一定形如 的形式,其中 为流量, 为此时其他边的费用。注意到最小费用流的过程形如不断跑最短路,跑出 时的 ,如果 那么就终止,否则将本次增广的流加入答案。因此可以预处理出 时每次跑出的 ,以及此时的流量及费用,那么对于每组询问可以通过二分找到对应的 为多少,即可回答询问。
复制代码- 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
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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2021-04-03 洛谷P5591 小猪佩奇学数学【单位根反演】
2021-04-03 【UOJ #214】合唱队形【min-max容斥】【DP】