物流问题引发的思考:2013苏杯赛C题,26秒:3小时

源于生活

物流问题向来都是采用线性规划,但是单一的线性规划往往只是简单地枚举,这是数学建模时最不愿意发生的。

今年的苏北赛C题:整车物流调度系统,数据量大,若要是以简单的相性规划来做显然是不现实的,我们以此题作为选拔题目,大部分队伍用各种软件、C/C++等程序来跑,效果极为不理想,时间开销非常大,多数都是2个小时以上,且各个队伍的结果相差较大。这就说明这种算法不易于实际操作,拼得是计算机硬件,而不是设计了。

还有一部分人喜欢用类似模拟退火、遗传算法等一些“靠运气”的算法,结果是能算出来,但是对于结果的好坏无法给与准确的评定,理论支持不甚完整。

基于上述情况,在当时做题时我们给出一个新的模型,对于类似问题简化非常有益。

高于生活

首先,我们先来分析一下物流问题的难点:
一、始发地有一批货物,一些运送的车辆,但是每辆车都有载重量的限制。在过路费、损耗费等相差不大(具体信息可参考2013苏北赛C题)的情况下。考虑空载费,本身对于货物的分配就存在困难,因为每辆车只能用一次,你不能保证每辆车都运送最合适的物资。
二、如果有一群始发地往一群接收地发送货物,你在建好最短路径的单行图后,对每辆车安排路线也会存在问题。因为考虑到一辆车可以沿途运送,对于路线的安排也将是非常困难。
三、从外地来的运货车在卸下本地所需的货物后,可以作为本地的运货车(时间卡的较松)。
根据这些问题,我先提出一个类似聚类的方法:城市群的概念:
一、如果某些城市的互通距离满足一定的限制,我们就可以将这些城市分配在一起形成城市群。
二、如果一个城市依靠有一个城市群,当它自身的订单运量较少时,便可以借助城市群里的其他城市来帮忙代运。
三、城市群之间可以以抽象车队形式进行大方向运送。

抽象发散

有一集合M,可以将M分成若干子集,且每个子集的数据个数不得超过n,每个子集中数据的总和不得超过sum(sum大于M中的每个元素,请在程序中自行检验),
对每种分配情况,若所有子集内容相同,但子集顺序不同的算不同情况,例如{{2,1},{1}} 与{{1},{2,1}}算不同情况;
对于每个子集中的数据顺序不同的算一种情况,例如一个子集{1,2}={2,1};
给出对M集合所有的分配情况,注意M中元素可以重复(已知条件为M,n,sum)。

具体事例:
M={1} ,n=3,sum无限制时:
1 — { {1} }

M={1,2} ,n=3,sum无限制时:
1 — { {2},{1} }
2 — { {1},{2} }
3 — { {1,2} }

M={1,2,3},n=3,sum无限制时:
1 — { {3},{2},{1} }
2 — { {3},{1},{2} }
3 — { {3},{1,2} }
……
10 — { {2,3},{1} }
11 — { {1,3},{2} }
12 — { {1,2},{3} }
13 — { {1,2,3} }
模型应用:
一、对已知的订单集合进行装车分配:设定M,设定sum(最大车载量限制)。
二、对运输车辆进行路线的分配:设定M(是在城市群分配好的基础上),设定n(一辆车最多可以运送的城市数)。
分析:
1、注意到子集是有顺序的
可以保证货物分配时对车的优先选择;
可以保证分配路线时车对目标城市的优先选择。
2、子集的元素是没有顺序的
这样保证了车在装一批货,不关心货物的装车顺序;
对于货车的要运送的目标城市,根据实际订单一般就能选择路径(先送量大的),没有排序的必要。
优劣分析:
优势:
1、用一种目的性较强的选择方案极大地减少了选车和路径分配的情况;
2、其中的参数可以人为控制,方便对不同情况提供合理的调整(n,sum可极大限制对M的拆分情况);
3、采用分区分治的思想,符合实际生活中物流产业对订单的分配。
劣势:

1、参数选择较麻烦;

2、如果M较大,则会耗时耗力,性能受影响(但是可以通过对sum限制减少时间开销,效果明显)。

推广:

1、对大部分物流问题求解都可以此计算。
2、划分出的城市群大小可变,也可对划分好的城市群递归继续划分,便于操作。
3、划分集合M时,通过对n和sum参数的调控可以极大减少分配情况,所以对大的集合也有很好的运行能力!

闲话说效果

对于本次我们做的苏北赛C题(针对第一小问),程序部分,其他队伍快则2小时,满则4小时,还有部分未给出结果,但是我们以此将性能提升为26秒(依个人电脑而定,但绝不会超过60s)。
思想较为新颖,因为当时我们未从网上得到有效信息(比赛结果没出来),绝对独家不重复。

主要代码

/*	
	创新函数,一种集合all的分配组合,
	可以设置每个子集含数据个数max_group,目前最大为8(54 5835 - 78s)
	可以设置每个子集所有数据的总和
*/
vector< vector< vector<int> > > all_combine(vector<int> all,
                                            int max_group,
                                            int max_sum)
{
	//首先遍历检验条件
	vector<int>::iterator iter_all;
	for(iter_all=all.begin();iter_all!=all.end();iter_all++)
	{
		if(*iter_all > max_sum)
		{
			cerr<<"不符合分配条件!"<<endl;
			exit(1);
		}
	}
	
	vector< vector<vector <int> > > result;
	int number_size=all.size(); 
	//递归终点
	if(number_size == 1)
	{
		vector< vector<int> > temp;
		temp.push_back(all);
		result.push_back(temp);
		return result;
	}
	
	int i=0;
	//设置每个子集中最大包含的数据个数
	int max=number_size<=max_group?number_size:max_group;
	for(i=1;i<=max;i++)
	{
		vector< vector<int> > arrange;
		int a[100], b[100];
		for(int j=0;j<number_size;j++)
			a[j]=j+1;

		arrange.clear();
		combine(a, number_size, i, b, i,arrange);

		vector< vector<int> >::iterator iter_arrange;
		for(iter_arrange=arrange.begin();iter_arrange!=arrange.end();iter_arrange++)
		{
			//检验头部集合的和是否满足条件
			vector<int>::iterator iter_int;
			int sum=0;
			for(iter_int=iter_arrange->begin();iter_int!=iter_arrange->end();iter_int++)
			{
				//注意位置编号是从0起!
				sum+=all[*iter_int-1];
			}
			if(sum > max_sum)continue;
			
			//递归用到,存放尾部——剔除已经排列好的部分
			vector<int> temp;
			//存放头部——已经排列好的部分
			vector<int> temp_pro;

			//生成头部和尾部
			int pos=1;
			vector<int>::iterator iter_all;
			for(iter_all=all.begin(),pos=1;iter_all!=all.end();iter_all++,pos++)
			{
				vector<int>::iterator iter_int;
				for(iter_int=iter_arrange->begin();iter_int!=iter_arrange->end();iter_int++)
				{
//					cout<<*iter_int<<" ";
					if(*iter_int == pos)
					{
						temp_pro.push_back(*iter_all);
						break;
					}
				}
				//说明arrange的一组中没有此数,添加到尾部
				if(iter_int == iter_arrange->end())
					temp.push_back(*iter_all);
			}

			//递归算出尾部情况
			vector< vector< vector<int> > > result_temp=all_combine(temp,max_group,max_sum);
			//自身情况
			if(result_temp.size() == 0)
			{
				vector< vector<int> > temp;
				temp.push_back(temp_pro);
				result.push_back(temp);
			}

			//将头部和所有尾部结合
			vector< vector< vector<int> > >::iterator iter_resultemp;
			for(iter_resultemp=result_temp.begin();iter_resultemp!=result_temp.end();iter_resultemp++)
			{
				vector< vector<int> > temp;
				//不是指上述的容器的迭代器
				vector< vector<int> >::iterator iter_temp;
				temp.push_back(temp_pro);
				for(iter_temp=iter_resultemp->begin();iter_temp!=iter_resultemp->end();iter_temp++)
				{
					temp.push_back(*iter_temp);
				}
				result.push_back(temp);
			}
		}
	}

	
	return result;
}
依赖(单一的组合):
//排列组合(无次序),n表示a的前n个数的组合
void combine(int a[], int n, int m, int b[], int M,vector< vector<int> > &arrange)
{ 
	int i, j;
	
	for (i = n; i >= m; i--)
	{
		b[m - 1] = i - 1;
		if (m > 1)
			combine(a, i - 1, m - 1, b, M,arrange);
		else
		{
			vector<int> temp;
			for (j = M - 1; j >= 0; j--)
			{
				temp.push_back(a[b[j]]);
			}
			arrange.push_back(temp);
		}
	}
}
如果有不解或是疑问欢迎讨论。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值