您的位置:首页 > 大数据 > 人工智能

【对偶定理】aizu2230

2014-03-17 10:26 337 查看
上学期写的一篇解题报告,还是传上来备份一下...

算法:单纯形or对偶定理化为最小费用流

对偶定理:

max{cx | Ax≦b, x≧0}

min{yb | yA≧c, y≧0}

原题大意:

给出一个n个点m条边的有向拓扑正权图,使得每个点都至少被一条由1到n的路径经过,现要求尽可能增加边权,使得由1到n的最长路不会增大。



抽象模型——>线性规划

设修改图后1到i的最长距离为f[i](f[i]为未知元),i到j的原权值为w[i][j],1到n的原最长路径为G,则有


会单纯形的神犇下面就不用看了...直接单纯形爆搞就是了...

首先对我们要最大化的式子进行化简,有


然后再对约束条件变形,化为


由于我们不会单纯形,所以接下来要开始用对偶定理,第一步是把原式化为矩阵形式



中间那个(m+1)*n的矩阵,前m行描述的是每个f[i]-f[j]<=-w[i][j],最后一行描述的是f
<=G,用矩阵乘法理解应该就没问题了。

接下来对照对偶定理的式子进行变形,化为



接下来分析这个些矩阵代表的式子,对于y[1]~y[m],每一个未知元所对应的权值是每条边的权值的相反数,那么是不是每个边都代表一个未知元呢?

接下来分析约束条件,每个约束条件的不等式的右边都是一个与节点i有关的常数d[i],然后分析左边的常数矩阵((m+1)*n的矩阵),前m行当第k行第i列一个位置是1的时候,当且仅当是在f[i]-f[j]<=-w[i][j]的时候,而w[i][j]恰好也是第k条边的权值,对偶之后就会发现,第k个未知元在考虑第i列的时候也恰好在这时候乘上了1,也就是说,我们之前猜测第k个未知元代表的是第k条边的猜想是对的,如果第k条边是i的出边,那么y[k]的系数就为1,同理如果第k条边是i的入边,那么y[k]的系数就为-1。

现在重新来抽出n个等式(暂时不考虑y[m+1]),第i个等式就变为



这个不等式比原来题目的不等式优的地方在于,每个未知元y[i]与原图的边是对应的,也就是说它出一次进一次,在不等式左右都会出现一次(常数为负的进行移项),而原来的不等式f[i]会出现在很多个不等式当中,这就提醒我们可以使用流量平衡的网络流模型。

原图的每个点就可以看做是一个不等式,每条边在权值取反后(min中的系数)就会变为一个未知元其容量为无穷,而常数d[i],如果d[i]为正,就新建源点s,由s向点i连一条容量为d[i],费用为0的边,表示是出边的未知元的值要比入边的未知元至少大d[i],同理,d[i]为负,新建汇点t,由i连向t,容量为-d[i],费用为0,表示之多可以小d[i]。

然后考虑y[m+1],他只与n相关,且根据对偶前的不等式,其系数为1,因此可以视作只作为出边的未知元,让n向t连一条容量为无穷,费用为G的边来表示这个未知元。

这样子做了一次最小费用最大流后,就可以把“出边的未知元的值要比入边的未知元至少大d[i]”这个条件满足,至于“之多可以小d[i]”,由于是大于等于的不等式,所以不流满也不会影响约束条件,但是这道题是要求最小,所以如果不流满,会导致答案变大。

我们考虑节点i,d[i]<0且i到t的边没流满,然后我们让1到i的某条路径上的未知元全部+1,即这条路径增广一次,首先对于起点1,由于是大于等于的符号,那么出边未知元怎么加都不影响条件,对于中间节点k,一进一出是流量平衡的,所以不影响不等式,对于终点i,由于没流满,所以可以接受这个流,不等式仍然成立,而我们知道原图都是正权,取反就变为负权了,因此结果就是在不影响不等式的情况使答案减小了。

为了解决这个问题,我们在残量网络上再跑一次最小费用流,新建新源点s’,t’,s’向1

连容量为无穷,费用为0的边,d[i]<0且i到t的边没流满的点,设d’[i]为i到t的剩余流量,

则由i向t’连容量为d’[i],费用为0的边。

考虑这样做完后能否继续把未知元变大,你把任意i->j的边变大,势必影响到节点j的不等式,这样子迭代下去,结果是影响到节点n的不等式,而为了使节点n的不等式满足,必须使y[m+1]的值变大,而我们考虑到y[m+1]的系数为G,也就是原图的最长路,因此不管怎么增,又势必会使答案变大,所以此时的答案便是原线性规划的最优解(注意要减去最开始的常数)。

注意这两次费用流要分开做的原因是最小费用最大流主要目的是最大流,有时候它会为了最大流不惜使答案非常不优,我们必须把它卡住,它才不会做傻事(或者你有兴趣写一个带下界的最小费用最小流ps:我也不知道能不能这么玩)。

当然,重新构造一下还是可以合并成一次费用流的(最好理解了原构图再来化简),我们发现,源点的容量上限和汇点的容量上限是相等的,也就是说,第一次网络流的时候,有些流经过n流向汇点,此时我们可以不把n连向汇点,而是连向1号点,就恰好顺便把第二次的费用流也做了。

仔细观察一下化简后的图,它和求上下界的网络流及其相似,这也就是日本的那个ppt里面讲到的建图。

这道题的第一步的关键就是对偶后,每个变量出现次数被限制,就会联想到流量平衡,第二步的关键就是这道题要处理的是不等式而不是等式,所以不能直接用流量平衡来做,而是要尽量将它卡到一个最优的状态。

未简化建图版,便于理解



#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int oo=1073741819;
using namespace std;
int tail[200000],d[200000],g[200000],ru[200000],chu[200000],v[200000],p[200000];
int next[2000000],flow[2000000],sora[2000000],po[2000000],st[2000000];
int cost[2000000];
int n,m,s,t,ss;
bool spfa(int s,int t)
{
	int h,r,ne,na;
	for (int i=1;i<=t;i++) d[i]=oo;
	h=r=0;
	st[r=1]=s,d[s]=0,v[s]=1,p[s]=0;
	for (;h<r;) {
		ne=st[++h];
		for (int i=ne;next[i];) {
			i=next[i],na=sora[i];
			if (flow[i] && d[ne]+cost[i]<d[na]) {
				d[na]=d[ne]+cost[i],p[na]=i;
				if (!v[na]) {
					v[na]=1;
					st[++r]=na;
				}
			}
		}
		v[ne]=0;
	}
	//cout<<d[t]<<endl;
	return d[t]<oo;
}
int widen()
{
	int sum=0,d=oo;
	for (int i=t;i!=s;i=sora[po[p[i]]]) 
		d=min(d,flow[p[i]]);
	for (int i=t;i!=s;i=sora[po[p[i]]]) {
		sum+=d*cost[p[i]];
		flow[p[i]]-=d,flow[po[p[i]]]+=d;
	}
	return sum;
}
void origin()
{
	s=n+1,t=s+1,ss=t;
	for (int i=1;i<=t;i++) tail[i]=i,next[i]=0;
}
void link(int x,int y,int z,int w)
{
	++ss,next[tail[x]]=ss,tail[x]=ss,sora[ss]=y,flow[ss]=z,cost[ss]=w,next[ss]=0;
	++ss,next[tail[y]]=ss,tail[y]=ss,sora[ss]=x,flow[ss]=0,cost[ss]=-w,next[ss]=0;
	po[ss]=ss-1,po[ss-1]=ss;
}
void bfs(int s,int t)
{
	spfa(s,t);
	g[t]=-d[t];
}
int main()
{
	freopen("i.in","r",stdin);
	freopen("i.out","w",stdout);
	scanf("%d%d",&n,&m);
	origin();
	int tot=0;
	for (int i=1;i<=m;i++) {
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++,y++;
		link(x,y,oo,-z);
		chu[x]++,ru[y]++;
		tot+=z;
	}
	bfs(1,n);
	for (int i=1;i<=n;i++)
		if (ru[i]>chu[i]) link(s,i,ru[i]-chu[i],0);
		else link(i,t,chu[i]-ru[i],0);
	//link(n,1,oo,g
);
	link(n,t,oo,g
);
	int nss=ss;
	int ans=0;
	for (;spfa(s,t);ans+=widen()) ;
	flow[nss]=flow[po[nss]]=0;
	for (int i=s;next[i];) {
		i=next[i];
		flow[i]=flow[po[i]]=0;
	}
	link(s,1,oo,0);
	//cout<<'%'<<endl;
	for (;spfa(s,t);ans+=widen()) ;
	//cout<<oo-flow[ss-1]<<endl;
	printf("%d\n",ans-tot);
	return 0;
}
精简版

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int oo=1073741819;
using namespace std;
int tail[200000],d[200000],g[200000],ru[200000],chu[200000],v[200000],p[200000];
int next[2000000],flow[2000000],sora[2000000],po[2000000],st[2000000];
int cost[2000000];
int n,m,s,t,ss;
bool spfa(int s,int t)
{
	int h,r,ne,na;
	for (int i=1;i<=t;i++) d[i]=oo;
	h=r=0;
	st[r=1]=s,d[s]=0,v[s]=1,p[s]=0;
	for (;h<r;) {
		ne=st[++h];
		for (int i=ne;next[i];) {
			i=next[i],na=sora[i];
			if (flow[i] && d[ne]+cost[i]<d[na]) {
				d[na]=d[ne]+cost[i],p[na]=i;
				if (!v[na]) {
					v[na]=1;
					st[++r]=na;
				}
			}
		}
		v[ne]=0;
	}
	return d[t]<oo;
}
int widen()
{
	int sum=0,d=oo;
	for (int i=t;i!=s;i=sora[po[p[i]]]) 
		d=min(d,flow[p[i]]);
	for (int i=t;i!=s;i=sora[po[p[i]]]) {
		sum+=d*cost[p[i]];
		flow[p[i]]-=d,flow[po[p[i]]]+=d;
	}
	return sum;
}
void origin()
{
	s=n+1,t=s+1,ss=t;
	for (int i=1;i<=t;i++) tail[i]=i,next[i]=0;
}
void link(int x,int y,int z,int w)
{
	++ss,next[tail[x]]=ss,tail[x]=ss,sora[ss]=y,flow[ss]=z,cost[ss]=w,next[ss]=0;
	++ss,next[tail[y]]=ss,tail[y]=ss,sora[ss]=x,flow[ss]=0,cost[ss]=-w,next[ss]=0;
	po[ss]=ss-1,po[ss-1]=ss;
}
void bfs(int s,int t)
{
	spfa(s,t);
	g[t]=-d[t];
}
int main()
{
	freopen("i.in","r",stdin);
	freopen("i.out","w",stdout);
	scanf("%d%d",&n,&m);
	origin();
	int tot=0;
	for (int i=1;i<=m;i++) {
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++,y++;
		link(x,y,oo,-z);
		chu[x]++,ru[y]++;
		tot+=z;
	}
	bfs(1,n);
	for (int i=1;i<=n;i++)
		if (ru[i]>chu[i]) link(s,i,ru[i]-chu[i],0);
		else link(i,t,chu[i]-ru[i],0);
	link(n,1,oo,g
);
	int ans=0;
	for (;spfa(s,t);ans+=widen()) ;
	printf("%d\n",ans-tot);
	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: