当前位置: 首页 > 科技观察

电影优惠券推荐策略——二分图最优匹配算法

时间:2023-03-21 22:15:58 科技观察

作者|刘杰问题概述一个订单最多可以使用优惠券中包含的电影票数量。也就是说,用户可以选择几个座位,最多可以使用几张优惠券。优惠券具有三个属性,即:面值(元):不支持票价时,票价小于或等于面值。使用固定支付金额(元):满足兑换券使用条件时需要支付的金额。补差价(是/否):若支持补差价,当票价大于面值时,需额外支付(票价-面值)元。例如:小明有面值50的兑换券,固定支付19元,支持补差价。那么他就可以用这张兑换券购买票价在50元以内的电影票,只需要支付19元。因为他支持补差价,他可以买到一张票价60元(大于面值)的电影票,需要支付(19+(60-50))也就是29元。我们的问题是:用户下单,订单中有x张(根据业务场景,x<=6)电影票和y张兑换券,从y张兑换券中选出不超过x张兑换券,这样这个订单的实际支付金额是最少的。如果有多个方案,则按照以下优先级为用户推荐优惠券选择方案:优先级1:选择实际支付金额最小的方案。优先级2:如果实际支付金额相同,优先使用面值小的方案原因:如果用户想购买票价38元的电影票,他目前有40元以及一张60元兑换券,而使用任意一张兑换券所能获得的实际支付金额为0元,所以优先为用户选择一张40元兑换券,这样60元兑换券才能生效用户的下一个订单并为用户省钱。优先级三:如果面值相同,则使用优惠券包中消费券数量最多的优惠券。优先级4:若消费券张数相同,则使用过期时间最早的一张。技术方案一:贪心具体步骤:排序将票价从大到小排序,使得票价高的电影票优先。目的是让金额大的电影票优先使用限制较少(均为面值)的兑换券,让券的面值得到充分的获取。利用,门票和优惠券的每种组合尽可能是最优解。证明方法:反例法不幸的是,很快就找到了一个反例来推翻这个计划。图中所示的反例是第二种方案:暴力枚举,枚举所有方案,从所有方案中寻找最优解,但是时间复杂度高达C(y,x)*A(x,x)这是O(n!)。按照计算机可以在1s内进行10^8次计算的标准,当n=13时,需要10s以上才能得到答案,而且n每增加1,时间就会扩大n倍。解法三:二分图最优匹配算法——KM算法KM(Kuhn-Munkres)算法介绍km算法是一种二分图最优匹配算法,主要用于解决一个经典的问题模型:完美婚姻问题。问题描述如下:n个男生和n个女生相亲,第i个男生和第j个女生在一起的幸福值为val(i,j),如何让n个男生和n个女生完成一对一匹配,使得这个整体的总幸福值最大。我们的问题有点类似于完美婚姻问题的模型,经查,km算法的时间复杂度为n^3。km算法的一大优点是可以查出哪张优惠券对应哪张电影票,适用于选座相关的业务场景。km算法实现(不熟悉km算法的同学可以先看第三部分)。我们将兑换券视为男孩,将电影票视为女孩。用兑换券j购买电影票i的成本为-w(i,j)建图,如果兑换券j不能购买电影票i,则成本w(i,j)设为负infinity,构造一个二部图来尝试求解,我们会遇到一些问题:变换1:如何满足km算法的使用条件?因为km算法是求解二部图的最佳匹配,也就是说二部图必须有最佳匹配才能被km算法求解。最佳匹配存在的必要条件是:两边的点必须相同,并且至少存在一种匹配方案,使得所有的点都匹配。所以我们需要填充点和边(填充点和边也是使用km算法的常用技术)。边填充策略:设置不存在边的权重为-inf。补货策略:添加x张兑换券,y+i张兑换券与第i张电影票相连,权重为电影票原价,这样可以排除无限结果。需要计算使用原价加购的情况。变换2:如何在多个解中找到满足优先级的解?我们可以把所有兑换券按照面值从小到大排序。如果面值相同,则按照入场时间从早到晚排序。如果结算时间相同,按照到期时间从早到晚排序。简而言之,优先将较高的优惠券放在前面。枚举数字k,将前k张代金券和所有电影票加入到km模型中,计算最小成本,只有当成本变得比之前更小时才更新答案。这确保在满足优先级的同时获得最少的成本。还有一个好处是,如果后续的pm调整了policy的优先级,那么我们就可以改变最初的排序规则。但是此时时间复杂度变成了n^4。如图所示,当k=2时,得到最优解,当k=3时,可以匹配优先级低的优惠券。变换三、时间复杂度的优化经过变换一和变换二后,算法的时间复杂度为:(x+y)^4+y*logy(x代表电影票张数,y代表兑换券张数)时间复杂度分析:补点运算后,二部图两边各有x+y个点。因为km算法的时间复杂度是n^3次方,n是二分图一侧的点数。所以当前的时间复杂度是(x+y)^3。为了处理匹配优先级的问题,我们对优惠券进行了优先排序。时间复杂度是y*logy。运行km算法时,枚举了第k个数,所以总时间复杂度为(x+y)^4+y*logy。转换方法的具体步骤:对每张电影票,预处理这张电影票优先级最高的n(n为当前电影票的张数),去掉这些兑换券,最多可以得到n张兑换券。使用nn优惠券替换所有原始优惠券。时间复杂度(经过优化,算法的时间复杂度与优惠券的数量无关,而是与电影票的数量有关。目前的业务场景是电影票不超过4张,所以算法有更好的性能):预处理x*x优惠券的时间复杂度:x^2*y*logx(x为电影票张数,y为优惠券张数)运行km算法求最优解的时间复杂度:((1+x)*x)^4+2*x^2*logx(x为电影票数)总时间复杂度:x^2*y*logx+((1+x)*x)^4+2*x^2*logx(x为电影票数,0匹配边->非匹配边->……->非匹配边路径。有的博客也叫交织路径)加一条匹配边边km依赖定理和证明定理:如果二分图中存在一组可行顶脚本,并且在可行顶的等子图中存在完美匹配-scripts,则匹配为原始二分图的最佳匹配。证明:考虑原二分图的任意一组完美匹配M,其边权重和val(M)等于每条匹配边的权重和(匹配边没有公共顶点),根据可行的定义topmark,我们可以得到任意一组完美匹配的边权重之和小于等于任意一组可行topmark的点权重之和。若存在一组可行上标,且在该可行上标的等子图中存在完美匹配,则等子图的完美匹配M'的边权和val(M')为:(因为相等子图中只有w(u,v)=l(u)+l(v)的边)显然对于任何完美匹配M,val(M)<=val(M'),所以M'是重量和最大值的完美匹配是最佳匹配。执行步骤是因为二分图两边的点数相等,假设个数为n。首先,我们需要初始化二分图的可行顶标。二部图左侧点的可行顶标值为:以该点为端点的最大边权,二部图右侧点可行顶标的值为:0。匹配左边的点,匹配准则为:可行顶标之和等于边权。(满足等子图)左边节点u的匹配规则是:如果能匹配到,则直接匹配;如果不能匹配,则以u为起点寻找相交路径,这些相交路径将形成一棵以节点u为根节点树的相交树,树中任意两条边满足匹配条件。如果存在与其父节点满足匹配条件且为非匹配边的叶子节点v(存在增广路径),则进行增广操作(取增广路径中匹配边的补集)添加匹配边。如果没有叶子节点满足匹配准则(叶子节点都是匹配点),那么调整可行顶标的值,如何调整呢?我们将交错树中二分图左边的点集记为S,交错树中右边的点集记为T,左边不在交错树中的点集记为S',而右边不在交错树中的点集合作为T'集合S中的点,可行顶标减少集合T中的slack_min点,可行顶标增加slack_min根据集合的左右顶点,我们可以将二分图中的边分为四种类型:左顶点在S中,右顶点在T中,可行顶和不变,等子图的左顶点在S,右顶点在T'中,可行顶标和变小,可以加入等子图,但需要满足可行顶的定义:可行顶之和大于或等于边权重和,所以我们需要让slack_min的值为min(l(u)+l(v)-w(u,v)),(u是S'中的一个点,v是集合T'中的点)S'中的左顶点,T中的右顶点,可行顶和变大,无法加入等子图S'中的左顶点,T'中的右顶点,当一个新的点u加入集合T有两种情况:如果是未匹配点,发现增广路和S'中的点已经匹配,继续增广,求u'使得每轮调整可行顶标,集合T至少加一个点,那么顶多修改n次顶标后,就可以找到增光路。代码运行过程以完美婚姻问题为例:现在有3男3女,不同的男生女生有不同的好感度值,如图(如果没有边则好感度为0),我们希望将它们成对配对以最大化整体好感度。初始化策略:构造一个满足w(u,v)<=l(u)+l(v)的可行顶标。构建方案:可行最高分对所有男生的取值:0,对所有女生的取值:最大好感度值为每个女生一一找对象。只有满足可行的顶边和等边权重才能配对雌一:第一轮雌一雄:10+0=10,配对成功。女二:第一轮:女二与男一:40+0!=20,配对失败,女二与男二:40+0=40,配对成功,女三:第一轮:女三和男二:110+0=110,但是男二和女二是配对的,让女二调,发现除了男二没有配对条件,所以配对女三男二失败了。失败的原因是男二女二配对,女二调不准。女三男三:110+0!=30,配对失败。第一轮匹配失败。被采访的女生是女二女三,被采访的男生是男二男一男三,男一至少需要调整20才能和女二配对成功,男三需要调整在至少80个才能成功配对。所以slack_min等于20。调整可行顶标,女2和女3减20,男3增加30,如下图:第二轮:女3和男2:90+20=110,但男2和女2配对,让女2尝试换搭档,发现男的符合要求,但是男的已经和女的配对了。试女一换对象,发现男三符合调节,于是女一换男三,女二换男一,女一换男一,雌性被雄性取代。第三个和男二配对,如图:递归版本的代码:#include#include#includeusingnamespacestd;constintMAXN=305;constintINF=0x3f3f3f3f;intlove[MAXN][MAXN];//记录每个女孩和每个男孩的好感度intex_girl[MAXN];//每个女孩的期望值intex_boy[MAXN];//每个男孩的期望值boolvis_girl[MAXN];//记录每轮匹配匹配到的女生boolvis_boy[MAXN];//记录每一轮匹配中匹配到的男生intmatch[MAXN];//记录每个男生匹配到的女生如果没有-1intslack[MAXN];//记录每个男人被女孩吸引的最小期望值intN;booldfs(intgirl){vis_girl[girl]=true;for(intboy=0;boy