首页 > 代码库 > uyhip问题 最苛刻的投票程序
uyhip问题 最苛刻的投票程序
一个国家里有 N 个公民,这些公民从 1 到 N 依次编号。这是一个民主国家,国家做出的每个决定都需要全体公民投票,每个人必须且只能投一票。
不过,随着该国家人口数量的增加,这种投票方式的效率越来越低。于是,这个国家实行了一种新的民主制度。每过四年,这个国家将会举行一次“代表选举大会”,届时,每个公民都必须且只能提名一个他信得过的人,来作为他自己的代表。注意,提名自己作为自己的代表也是允许的。对于每个被提名了的人,有百分之多少的人提名他,他就拥有了相当于多少张选票的权力(向下取整)。在接下来的四年里,国家要做出某项决定时,就只需要这些代表来参加了。
比方说,这个国家有 200 个人,在代表选举大会上,有 98 个人提名 1 号公民当代表,有 101 个人提名 2 号公民当代表,有 1 个人提名 200 号公民当代表。结果就是,只有 1 号公民和 2 号公民成为代表,在接下来的四年里参与投票,其中 1 号公民一票当 49 票,2 号公民一票当 50 票。值得注意的是, 200 号公民虽然有提名,但支持率仅 0.5% ,因此他今后四年没有当代表的权力。
为了支持新的民主政策,你需要设计一套算法,来计算每届代表选举大会结束后,哪些公民成为了代表,他们手中各自有多少票的权力。程序的输入数据来源于一盘磁带。磁带上有 N 个数,分别记录了这 N 个公民各自提名的代表的编号(由于提名是匿名的,磁带上的数据不是顺序的,你无法判断出每个数都是谁的)。程序可以多次读取磁带,但是每次都只能是从头到尾依次读取每一个数。由于这个国家的人数已经增加到了一定规模,因此你的程序必须非常高效。具体地说,你的算法必须要满足以下几点限制:
1. 你的程序读取磁带的次数要尽可能的少;
2. 磁带是只读的;
3. 程序可以在自己的内存里储存变量,不过只能使用 O(1) 个单元的空间(即所耗费的内存空间与 N 无关);
4. 内存里每个单元的空间只能储存 0 到 N 之间的整数(包括 0 和 N );
5. 预处理阶段完成后,程序将进入询问阶段,即针对形如“公民 x 获得了多少票的权力”的问题给出回答。一旦预处理完成进入询问阶段后,程序就不能再接触磁带了。
现在的问题是,在最坏情况下,最少需要读取多少次磁带?给出一个满足要求的算法,并证明读取磁带的次数已经不能再少了。
已知量:候选人最多100个
约束条件:)每个人获得的票数大于等于 N/100才能获选
)投票序列只能顺序遍历,不能随机遍历;可以多次遍历;序列不能被修改
)使用的内存为O(1),每个内存单元只能存0到N的整数
)必须回答任何人获得的投票数,询问的对象可以是任何人
)尽可能少地遍历序列
未知量:设计一个算法,最后能找出所有候选人的选票数
候选人最多有100个,所以最少需要100的内存空间
任何人的数字小于1%,权利值即为0,不需要记录他获得的选票数。因此问题可以理解为 找出序列当中所有出现了大于1%数字
尝试减少约束条件,如果可以使用更多的内存空间。我们可以使用O(n)的空间统计每个数出现了多少次。
然后收集计数>=1%的数字。如此一来,“)每个人获得的票数大于等于 N/100才能获选”这个条件没有用上(你是否用到了所有条件)
所以这个问题的关键是:如何利用一些数字的占比>=%1这个条件
为了便于思考,我们把问题特殊化,寻找序列中1/2以上的数字。这个问题似乎简单一些。(特殊化)
由于看过《编程之美》,能够想起”寻找发帖水王“这个问题,正是寻找出现一半以上的数字。(你是否能够想到一道相似的问题?)
寻找ID序列当中出现一半以上的ID
这个问题的解答非常巧妙。只需要遍历一遍序列。最关键的是如何利用有一个数字出现了一半以上。
内存字典,记录两个数字和这两个数字的计数。遍历过程中,如果凑齐了两个数字,就把它们计数减1。如果计数为0,就把这个数字丢掉。这个操作称为清理
遍历序列时,如果这个数字记录在字典中,把内存的计数加1。否则添加到字典中,计数设为1。然后,执行清理操作。
每一次都会清理两个不同的数字,目标数字还是保持一半以上,问题的规模减小了。遍历完所有序列以后,最后只会剩下一个数字。
扩展问题:如果有3个数字都超过1/4,剩下的数字不到1/4,这个问题如何解答?
还是使用上面的方法,只是字典中需要记录4个数。
按照这种方式如果有99个数字超过了1%,剩下的数不到1%,是不是就和原先的问题等价?
并非如此,我们的问题当中超过1%的数字可以有0到100个,未必是99个。
尝试使用上面的方法,运用到我们问题的情景当中。最后的情况是如何的?(是否能运用它的方法?)
似乎,出现次数大于等于1%的数都会出现在我们的字典当中,但是少于1%的数也会出现在我们的字典当中。
当然需要注意一些细节:如果序列当中有100个数,每个占比1%,如果在遍历完最后一个数以后,这时候每个数的计数都是1,再次执行清除,会导致所有的数都会被丢弃。
概括地说,最后一次的清理,会导致占比小于等于1%的数字被清理掉。占比1%的数是需要记录下来的。遍历最后一个数后不能做清理。
1)首先,第一次遍历完以后,字典中有100个数,其中保留了占比大于等于1%的数字,还有一些占比小于1%的数字。
2)然后,我们只要再遍历序列一遍,统计这100个数出现了多少遍。
3)最后,再收集第二次遍历中出现次数大于等于1%的数,以及它们的的计数。