2017华为软挑——最小费用最大流(MCMF)

1. 概述

1.1 最小费用最大流

今年的华为软件精英挑战赛是要在一张给定的流量网络中,找到合适服务器部署地点、最佳路由路径使得服务器到达消费节点的费用在满足流量需求的时候费用最小。因而在服务器给定的情况下就变成了,最小费用最大流问题了。


首先最小费用最大流问题:已知容量网络D=(V,A,C),每条弧(Vi,Vj)除了已给出容量Cij 外,还给出了单位流量的传输费用Bij ≥0,记作D=(V,A,C,B),其中Bij ∈B。要在费用、容量网络D中寻找Vs—>Vt的最大流f,且使流的总传输费用最小



对于最大流问题:
最大流的求法就是在容量网络上从某个可行流出发,设法找到一条从Vs—>Vt的增广链,然后沿着此增广链调整流量,作出新的流量增大了的可行流。在这个新的可行流基础上再寻找它的增广链。如此反复进行,直至再找不出增广链时,就得到了该网络的最大流。

最小费用最大流问题:
就先找一个最小费用可行流,再找出关于该可行流的最小费用增广链,沿此链调整流量,则得到一个新的流量增大了的最小费用流,然后对新的最小费用流重复上述方法,一直调整到网络的最大流出现为止,便得到了所考虑网络的最小费用最大流。
具体的原理性说明可以参考这里

1.2 最短路算法
最短路算法在本赛题中占有至关重要的作用,算法运行的效率和速度对比赛成绩影响很大。(对于网络节点数目为800的图在我的本子上要跑200多ms,但是大佬写的借鉴ZKW的最短路算法,效率不知道高多少倍去了-_-||)。
SPFA算法估计是使用得比较多的方法了。SPFA的原理其实与Dijkstra原理上相似。只是它使用了一个队列去维护搜索最短路,减少了冗余。具体的原理的话这里就不再多赘述。

2. 编码

网络图数据结构:
#define MAX_NET_NODE 1000       //最大网络节点
#define MAX_CLIENT_NODE 500     //最大消费节点

//网络中的网络节点的输入和输出节点
struct NODE
{
    std::vector<int> innode;    //指向该节点的节点
    std::vector<int> outnode;   //从该节点值出去的节点
};
NODE net_node[MAX_NET_NODE];    //网络节点的存储

//网络中的消费节点的输入和带宽需求
struct CLIENT_NODE
{
    std::vector<int> innode;    //指向该节点的节点
    int bandwidth;   			//从该节点值出去的节点
};
CLIENT_NODE client_node[MAX_CLIENT_NODE];   //消费节点的存储
int client_innode[MAX_NET_NODE];   //存储与消费节点相连的节点
int note_to_client[MAX_NET_NODE];   //根据与消费节点相连的节点找到消费节点
int needed_flow=0;                  //在本case中需要的总流量

//每条边的属性:总带宽,带宽价格
struct EDGE_ATRRIBUTE
{
    int bandwidth;  //每条边的总带宽
    int bandcost;    //每1G带宽需要的费用
};
EDGE_ATRRIBUTE net_edge[MAX_NET_NODE][MAX_NET_NODE];    //存储图数据的矩阵
最小费用最大流(MCMF)(添加了SLF和LLL(-_-||)):
#define INFINITE 1 << 26
#define MAX_NODE 1005
//#define MAX_EDGE_NUM 40005
struct Edge{
    int to;
    int vol;
    int cost;
    int next;
};
Edge gEdges[MAX_EDGE_NUM];

int gHead[MAX_NODE];	//
int gPre[MAX_NODE];		//
int gPath[MAX_NODE];	//路径
int gDist[MAX_NODE];	//距离

int gEdgeCount;

*最小费用最大流 start*/
void my_search_init(std::vector<int> server_pos)
{
	path.clear();	//清空路径信息
	//min_server.clear();	//清空服务器的数组
	//初始化数据
	for(int i=0; i<MAX_NODE; i++)
	{
		gHead[i] = -1;
		gPre[i] = -1;
		gPath[i] = -1;
		gDist[i] = INFINITE;
	}
 	gEdgeCount = 0;
	//加普通节点入边
#ifdef MYDEBUG
    cout << "普通网络节点的边初始化:" << endl;
#endif
	int start(net_node_num);	//超级起点
	int end(net_node_num+1);	//超级终点
#ifdef MYDEBUG
	int edge_count(0);
#endif
	for(int i=0; i<net_node_num; i++)
	{
		int temp_start(i);
		if(CheckIsServer(i, server_pos))	//是服务器节点
		{
			//加边
#ifdef MYDEBUG
			cout << "服务器节点:" << i << endl;
#endif
			for(unsigned int j=0; j<net_node[temp_start].outnode.size(); j++)
			{
            	int temp_end = net_node[temp_start].outnode[j];
				//if(CheckIsServer(temp_end, server_pos)) continue;	//如果是服务器节点跳过
            	int bandwidth = net_edge[temp_start][temp_end].bandwidth;
				int cost = net_edge[temp_start][temp_end].bandcost;
#ifdef MYDEBUG
    			cout << temp_start << "======>" << temp_end << " , " << bandwidth << " , " << cost << endl;
				cout << temp_end << "======>" << temp_start << " , " << bandwidth << " , " << cost << endl;
				edge_count++;
				edge_count++;
#endif
				InsertEdge(temp_start, temp_end, bandwidth, cost);
				InsertEdge(temp_end, temp_start, bandwidth, cost);
			}
			for(unsigned int j=0; j<net_node[temp_start].innode.size(); j++)
			{
            	int temp_end = net_node[temp_start].innode[j];
				//if(CheckIsServer(temp_end, server_pos)) continue;	//如果是服务器节点跳过
            	int bandwidth = net_edge[temp_start][temp_end].bandwidth;
				int cost = net_edge[temp_start][temp_end].bandcost;
#ifdef MYDEBUG
    			cout << temp_start << "======>" << temp_end << " , " << bandwidth << " , " << cost << endl;
				cout << temp_end << "======>" << temp_start << " , " << bandwidth << " , " << cost << endl;
				edge_count++;
				edge_count++;
#endif
				InsertEdge(temp_start, temp_end, bandwidth, cost);
				InsertEdge(temp_end, temp_start, bandwidth, cost);
			}
#ifdef MYDEBUG
			cout << "服务器节点:" << i << endl;
#endif
			continue;
		}

        for(unsigned int j=0; j<net_node[temp_start].outnode.size(); j++)
		{
            int temp_end = net_node[temp_start].outnode[j];
			if(CheckIsServer(temp_end, server_pos)) continue;	//如果是服务器节点跳过
            int bandwidth = net_edge[temp_start][temp_end].bandwidth;
			int cost = net_edge[temp_start][temp_end].bandcost;
#ifdef MYDEBUG
    		cout << temp_start << "======>" << (temp_end) << " , " << bandwidth << " , " << cost << endl;
			cout << temp_end << "======>" << temp_start << " , " << bandwidth << " , " << cost << endl;
			edge_count++;
			edge_count++;
#endif
			InsertEdge(temp_start, temp_end, bandwidth, cost);
			InsertEdge(temp_end, temp_start, bandwidth, cost);
		}
	}

	//加入超级起点节点的边
#ifdef MYDEBUG
    cout << "超级起点节点的边初始化:" << endl;
#endif
	for(unsigned int i=0; i<server_pos.size(); i++)
	{
		int temp_end = server_pos[i];
#ifdef MYDEBUG
    	cout << start << "======>" << temp_end << " , " << needed_flow << " , " << 0 << endl;
		edge_count++;
#endif
		InsertEdge(start, temp_end, MAXINT, 0);
	}

	//加入超级终点节点的边
#ifdef MYDEBUG
    cout << "超级终点节点的边初始化:" << endl;
#endif
	for(int i=0; i<client_node_num; i++)
	{
		int temp_start = client_node[i].innode[0];
		int bandwidth = client_node[i].bandwidth;
#ifdef MYDEBUG
    	cout << temp_start << "======>" << end << " , " << bandwidth << " , " << 0 << endl;
		edge_count++;
#endif
		InsertEdge(temp_start, end, bandwidth, 0);
	}
#ifdef MYDEBUG
    cout << "加入的总边数:" << edge_count << endl;
#endif
}

bool CheckIsServer(int node_id, std::vector<int> server_pos)
{
	int size(server_pos.size());
	if (0==size) return false;
	for(int i=0; i<size; i++)
	{
		if(node_id == server_pos[i]) return true;
	}
	return false;
}

//求出最小费用最大流
int MinCostFlow(int s, int t, int& flow)
{
    int cost = 0;
	path.clear();
    while (Spfa(s, t))
	{
        int f = INFINITE;
		std::vector<int> temp_path;
        for (int u = t; u != s; u = gPre[u])
		{
            if (gEdges[gPath[u]].vol < f)
                f = gEdges[gPath[u]].vol;
			temp_path.push_back(gPre[u]);
        }	//找到增益的最小流
        //cout << "increaseed flow:" << f << endl;
		temp_path.push_back(f);
        flow += f;
        int temp = cost;
        //cost += gDist[t] * f;
        for (int u = t; u != s; u = gPre[u])
		{
            gEdges[gPath[u]].vol -= f;   //正向边容量减少
            //gEdges[gPath[u]^1].vol += f; //反向边容量增加
			cost += gEdges[gPath[u]].cost*f;
        }	//设置路中的容量变化
        temp_path.push_back(cost-temp);	//记录这条路的价格
		path.push_back(temp_path);
		if(flow >= needed_flow)	//找到需要的流量就可以了,不用找完所有的曾广路线
			return cost;
    }
    return cost;
}

//假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
bool Spfa(int s, int t)
{
	for(int i=0; i<(net_node_num+2); i++)
	{
		gPre[i] = -1;
		gDist[i] = INFINITE;
	}
	bool isfind = false;	//是否找到元素的标志位
    gDist[s] = 0;
    //int sum(0);				//LLL中计算总的和的变量
    std::deque<int> Q;
    Q.push_back(s);
    while (!Q.empty()){//由于不存在负权和环,因此一定会结束
        int u = Q.front();
        Q.pop_front();
        //LLL
		//while (true && !Q.empty())
		//{
			//int dist = Q.front();	//队首元素
			//if (gDist[dist]*Q.size() > sum){Q.pop_front(); Q.push_back(dist);}
			//else{break;}
        //}

		if (isfind)	{break;}

        for (int e = gHead[u]; e != -1; e = gEdges[e].next){
            int v = gEdges[e].to;
            if (gEdges[e].vol > 0 && (gDist[u] + gEdges[e].cost) < gDist[v])
			{
                gDist[v] = gDist[u] + gEdges[e].cost;
                gPre[v] = u; //前一个点
                gPath[v] = e;//该点连接的前一个边
                //sum += gDist[v];	//LLL
				//SFL
				if (!Q.empty())
				{
					if (gDist[v] > gDist[Q.front()])
					{
						Q.push_back(v);
					}
					else
					{
						Q.push_front(v);
					}
				}
				else
				{
					Q.push_back(v);
				}
                //Q.push(v);
				if (v == t)
				{
					isfind = true; break;
				}
            }
        }
    }

    if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
        return false;
    return true;
}

//加入边
void InsertEdge(int u, int v, int vol, int cost)
{
    gEdges[gEdgeCount].to = v;
    gEdges[gEdgeCount].vol = vol;
    gEdges[gEdgeCount].cost = cost;
    gEdges[gEdgeCount].next = gHead[u];
    gHead[u] = gEdgeCount++;
}
/*最小费用最大流 end*//

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值