研究!rsc公司

关于编程的想法和链接,通过

RSS公司

选举规划
发布于2008年6月13日,星期五。

几天前,网站fivethirtyeight.com网站提出了这样一个问题:以最少的州数赢得选举团,也就是说,一组州的投票总数达到270票或更多,但如果从集合中删除任何状态降至270以下。

数学家Isabel Lugo把手伸进工具箱并推出了组合学中最值得信赖的工具:生成函数。伊斯最近的博客帖子给人一种美好的感觉解释。

我是一名程序员,所以我把手伸进工具箱找出我最喜欢的组合学工具:动态编程。动态编程本质上是一个奇怪的名称缓存的递归,减去递归。

让我们考虑一个更简单的计数问题总共有270张选举人票。我们可以写一个简单的递归方程来计算这个数方式:

方式(n,v)=方式(n-1,v票[n])+方式(n-1,v)

方式(n,v)是方法的数量,仅使用第一个n个州,得到v(v)投票。要么包括州n个要么投票,要么你不投票。如果你这样做,那么你需要v票[n]从第一名投票n-1个如果你没有,那么你需要v(v)来自第一n-1个状态。方式(n,v)只是数字的总和实现这两个简单场景中任一场景的方法。(当然有一个基本情况:使用前0个状态,有一种方法获得0票,无法获得任何其他票数。也没有办法,使用任何数量的状态,获得的票数为负数。)

这就形成了一个简单的递归程序:

整数64方式(int n,int64 v){如果(n==0&&v==0)返回1;如果(v<0|n<=0)返回0;回程(n-1,v票[n-1])+回程(n-1,v);}printf(“精确获取270=%lld\n的方法”,方法(51270));

(哥伦比亚特区没有任何国会议员,但确实有选举人投票,因此51票。上述讨论讨论了票数[n]好像投票是1索引的,但在C中是0索引的,因此票数[n-1].)

每次呼叫方式(n,*)导致两次调用方式(n-1,*),造成一个不幸的O(2N个)运行时间;因为N=51,所以花时间写一个更快的程序是值得的。

有2个51函数调用,但可能只有52*271个不同函数参数;因此递归程序重复相同的调用数十亿次。如果我们添加缓存,我们可以避免重复:

int64缓存[52][271];//初始化为-1整数64方式(int n,int64 v){如果(n==0&&v==0)返回1;如果(v<0|n<=0)返回0;if(缓存[n][v]==-1)缓存[n][v]=方式(n-1,v-投票[n-1])+方式(n-1,v);返回缓存[n][v];}printf(“获取270的方法=%lld\n”,方法(51,270));

现在,在第一次呼叫方式,最多有两个电话填充每个缓存条目,或最多2个*52个*271个调用。那会跑的许多的速度更快。

我们可以到此为止,但我们可以进一步简化代码通过删除递归并通过迭代填充缓存。

int64路[52][271];//初始化为0方式[0][0]=1;对于(n=1;n=51;n++){对于(v=0;v<=270;v++){方式[n][v]=方式[n-1][v];if(v票[n-1]>=0)方式[n][v]+=方式[n-1][v-投票[n-1];}}printf(“获取270的方法=%lld\n”,方法[51][270]);

在迭代进行计算时,这一点很重要方式[n][v],它已经按照需要的方式计算了条目。方式(n,*)取决于方式(n-1,*)它足以填满整个方式[n-1]在开始之前方式[n].没有必要迭代v(v)从0到270。我们可以从270转为0:

int64路[52][271];//初始化为0方式[0][0]=1;对于(n=1;n<=51;n++){对于(v=270;v>=0;v---){方式[n][v]=方式[n-1][total];if(v票[n-1]>=0)方式[n][v]+=方式[n-1][v-投票[n-1];}}printf(“获取270的方法=%lld\n”,方法[51][270]);

事实上,方式[n][v]取决于方式[n-1][u]只为u小于等于v,所以如果v(v)从270迭代到0,我们可以重用单个表行(我们也可以借此机会替换n-1个具有n个,既然n个没有索引到方式更多):

int64路[271];//初始化为0方式[0]=1;对于(n=0;n<51;n++)对于(v=270;v>=票[n];v---)方式[v]+=方式[v-投票[n]];printf(“ways to get actually 270=%lld\n”,ways[270]);

许多动态编程解决方案都具有此特性,您只需要下面和左边的数组项,因此如果您从右边迭代,您可以只保留一行。对于这个问题,节省的空间并不显著,但在一些问题上确实如此。

现在我们有了一个非常简单、直接的方法计算获得270票的方法的数量,但不是最初的问题。最初的问题是有多少方法可以获得至少270票,但具有最小状态集。

我们可以计算出获得270票、271票、272票等的方法。,但对于较大的数量,我们需要确保只包括使用最小集的方法。我们可以通过使用状态来确保最小化n个只有的票如果总数还不够大:

int64路[400];//初始化为0方式[0]=1;对于(n=1;n<=51;n++)对于(v=270+票[n]-1;v>=票[n];v---)方式[v]+=方式[v-投票[n]];

这个+=将永远不会添加方式[u]对于任何u>=270.方式[v]是准确获取v(v)投票其中一组状态相对于270是最小的。要获得至少270张选票的方法数量,求和数组的末尾:

总计=0;对于(v=270;v<400;v++)总计+=方式[v];printf(“获取至少270的最小方法=%lld\n”,总计);

400的上限只是一个足够大的数字:任何最低限度的获胜组合都不可能获得超过400张选票*

这里有一个微妙之处:我们只添加状态n个的选票如果投票总数超过270票,但也许状态n个拥有55票,目前总数为269票。正在状态中添加n个总数超过270,但在那里一定是269个州中的较小州,所以不是最小集。要避免这种情况,只需考虑每个州按大小排序,从多数票到最少票。然后,当我们添加一个状态时,正在考虑的集合只能包含更大的状态,因此上面的代码可以计算期望的答案。

具体来说,下面有一个完整的C程序。它在我的Thinkpad X40上运行大约80微秒。这比等待任何O(2)都要快得多51)本来会的。

我认为有两个如此不同的人真是太棒了思考相同计算的方法:抽象泛函生成函数的优雅性和命令的直接性动态规划。那些更喜欢一个的人方法或其他方法可以选择适合他们的方法。就个人而言,我是卢戈认为会说“通过生成函数的方法只是动态编程符号无缘无故地四处飘荡。”**


*事实上,由于得票最多的州有55票,没有最低票数获胜一局可能有超过324票,但因为你不能大州获得269票,324票不可能最小集。因此,实际的上限甚至更小。事实上,如果你把得票最多的州加起来,你需要第一个11人达到271人,最后胜利的州有15个。因此,使用284作为上限就足够了而不是400。这种题外话正是400已经足够了!


**类似的对应关系是静态单一分析(SSA)表和连续传递样式(CPS).很容易看到SSA拥护者说CPS只是SSA有一堆额外的lambdas浮动没有什么好理由!



#包括<stdio.h>typedef long long int64;int票[51]={55, 34, 31, 27, 21, 21, 20, 17, 15, 15,15, 13, 12, 11, 11, 11, 11, 10, 10, 10,10,  9,  9,  9,  8,  8,  7,  7,  7,  7,6,  6,  6,  5,  5,  5,  5,  5,  4,  4,4,  4,  4,  3,  3,  3,  3,  3,  3,  3,三,};int64路[400];整数main(int argc,char**argv){int n,v,代表;总计int64;对于(n=0;n<400;n++)方式[n]=0;方式[0]=1;对于(n=0;n<51;n++)对于(v=270+票[n]-1;v>=票[n];v---)方式[v]+=方式[v-投票[n]];总计=0;对于(v=270;v<400;v++)总计+=方式[v];printf(“%lld\n”,总计);返回0;}

(评论最初通过Blogger发布。)

  • 伊莎贝尔·卢戈 (2008年6月13日上午6:32)谢谢!我很高兴看到有人记录了这一点。

    老实说,你的解决方案或类似的解决方案可能是我的解决方案的幕后黑幕;最后,我把计算工作外包给了Maple,我不知道它在做什么。

  • 杰克 (2008年6月15日下午2:12)O(2^51)不是等于O(1)吗?

  • 斯蒂芬·施罗德 (2008年6月15日下午4:35)杰克,是的。但是2^51只与美国的情况有关,而与问题的一般实例无关n个状态,提供运行时O(2^n),不是O(2^51)=O(1).

    请注意,即使函数f(n)可能由克(n)来自一些编号0今后,选择一个O(克(n))a上的算法O(f(n))算法,如果知道典型输入将有大小n<n0.

  • S公司 (2008年6月16日上午11:23)真正的问题不在于奥巴马或麦凯恩在分裂严重的战场州中表现如何,而在于我们首先不应该有战场州和旁观者州。在总统选举中,每个州的每一次投票都应该与政治相关。而且,每一票都应该平等。我们应该在全国范围内对总统进行全民投票,其中白宫将投票给在所有50个州中获得最受欢迎选票的候选人。

    全国普选法案将保证在所有50个州(和华盛顿特区)获得最受欢迎选票的候选人当选总统。该法案只有在拥有大多数选举人投票权的州以同样的形式颁布,即有足够的选举人投票选举总统(538人中的270人)时才能生效。该法案生效后,来自这些州的所有选举人票将授予在所有50个州(和华盛顿特区)获得最受欢迎选票的总统候选人。

    现行总统选举制度的主要缺点是,总统候选人没有理由在他们安全领先或无望落后的州进行投票、访问、宣传、组织、竞选或担心选民的担忧。这是因为“赢家通吃”规则将一个州的所有选举人票授予在每个州获得最多选票的候选人。由于这一规则,候选人将注意力集中在少数几个分裂紧密的“战场”州。三分之二的访问和资金集中在六个州;88%来自9个州,99%的资金只流向16个州。三分之二的州和人民只是总统选举的旁观者。

    现行制度的另一个缺点是,候选人可以在没有赢得全国最受欢迎选票的情况下赢得总统大选。

    全国普选法案已获得18个立法院的批准(科罗拉多州、阿肯色州、缅因州、北卡罗来纳州、罗德岛州和华盛顿州各有一个院,马里兰州、伊利诺伊州、夏威夷州、加利福尼亚州和佛蒙特州各有两个院)。该法案已在夏威夷、伊利诺伊州、新泽西州和马里兰州制定为法律。这些州拥有实施这项立法所需的270张选举人票中的50张(19%)。

    请参见http://www.NationalPopularVote.com

  • 俄罗斯考克斯 (2008年6月16日下午12:10)@s: 欢迎访问政治博客。这个博客只关心选举学院,因为它会导致有趣的编程问题。

    我并不是说你的评论是对的还是错的,只是说它在这里不合适。

  • 怪物 (2009年8月8日上午11:02) 此帖子已被作者删除。

  • 怪物 (2009年8月8日上午11:04)不错!!从没想过这么容易。。

    看看我的。。如果你感兴趣,请发表评论。。www.spyfree.info网站

  • 小提琴手 (2011年3月26日下午4:07)轻微打字错误:

    方式[n][v]=方式[n-1][total];

    应该是

    方式[n][v]=方式[n-1][v];