http://hero.pongo.cn/Question/Details?ID=161&ExamID=156
So,想来现场参加火热决赛直取mini ipad的,请先通过线上初赛题目AB数:
给定两个正整数a,b,分别定义两个集合L和R,
集合L:即把1~a,1~b中整数乘积的集合定义为L= {x * y | x,y是整数且1 <= x <=a , 1 <= y <= b};
集合R:1~a,1~b中整数异或的集合定义为集合R= {x ^ y | x,y是整数且1 <= x <=a , 1 <= y <= b},其中^表示异或运算。
现从L中任取一个整数作为A,从R中任取一个整数作为B,如果必要在B的左边补0,使得B达到:“b的位数+1”位(十进制),然后把B接到A的右边,形成的一个十进制数AB。求所有这样形成数的和。
输入a,b 1<=a<=30, 1<=b<=10000000。
输出所有产生的AB数的和,由于结果比较大,输出对1000000007取余数的结果。
例如:a = 2, b = 4,
则L= {1 * 1, 1 * 2, 1 * 3, 1 * 4, 2 * 1, 2 * 2, 2 * 3, 2 * 4} = {1, 2, 3, 4,6, 8}
R = {1^1,1^2,1^3,1^4,2^1,2^2,2^3,2^4} = {0, 1, 2, 3, 5, 6}
相接的时候保证R中的数至少有两位,所以相接后所有的AB数的集合是
{
100,101, 102, 103, 105, 106,
200,201, 202, 203, 205, 206,
300,301, 302, 303, 305, 306,
400,401, 402, 403, 405, 406,
600,601, 602, 603, 605, 606,
800,801, 802, 803, 805, 806
}
输出它们的和:14502。
题外话:
这道题卡了好久,提交了18次,最后算是过了,出问题的地方居然是long和int 的溢出问题。其实早就该知道是这个问题,只是一直怀疑自己的算法是不是写错了,调试算法花了很久,结果算法没错,是代码写错了。
算法复杂度30*7000*2*30
这个已经优化到了极限了。
思路:
首先可以简单的证明,集合A与B组合出来的数字不存在重复。
证明:假设有重复,即:
a1b1
a2b2
那么b1和b2的位数肯定不等,假设b1的位数大于b2,那么因为b2至少是(b+1)位,所以b1至少是(b+2)位。而a<=30 , 在y<b的情况下 y^a 不可能是b的100倍,也就是说,y和a取异或,使得y增加的增量不会超过两个数量级。因此集合A与B组合出来的数字不存在重复。
基于以上结论,那么,cnt[0]和cnt[1]分别表示集合A和B的元素个数,sum[0]表示集合A中所有的数的和(sum[1] 集合B)
那么 题目要求的数 = sum[0]*cnt[1]*base(b+1)+ sum[1]*cnt[0] base(b+1) 表示“b的位数+1”
现在,问题转化为如何计算两个集合的大小,以及元素和。
首先说集合B吧,相对来说比集合A简单。
假设 a>b 那么 在计算x^y的时候,因为x<=30 所以 x能影响y的比特位是有限的,最多影响y的后六位。
因此,可以产生这样的思路:假设x的二进制表达长度=5 (例如30, x=11110) , 那么把y的后五位都清零,比这个数小的都在集合B中。然后从这个数开始枚举(判断比它大的数是否可以用x^y表示),一直枚举到y的后五位全是1,枚举的代价是30,每次判断的代价是30。总共计算B的复杂度是O(900)。
计算集合A,比较复杂,需要用到鸽巢原理。
假设集合Ai是[1,b]区间内所有整数乘以i得到的集合。
集合A=
A1+A2+...+Aa
- A1∩A2 - A1∩A3 - ...- Aa-1∩Aa
+ A1∩A2∩A3+ A1∩A2∩A4...
-...
规律就是加减交替,然后每次集合求交的数量会增加,一直都最后所有的Ai求交。
上面集合A的表达式有 C(a,1)+C(a,2)+...+C(a,a)=2^a 项计算复杂度非常大。
但是,这里有一个后门,也是这道题 b<=10000000的原因。
增因为在b<10000000的情况下,当集合Ai求交的数量增加到一定程度时,结果都是空集,
比如假设A1∩A2是空集那么就没必要计算A1∩A2∩A3了,因此就可以剪枝。
最后说求集合交集的方法:
假设集合Ai和集合Aj有一个元素z是相同的,即
z=i*b1
z=j*b2
假设i和j的最大公约数是g,即
i=g*ii
j=g*jj
那么
z=g*ii*b1
z=g*jj*b2
因为ii和jj的最大公约数是1,(i与j互素),因此b1一定有约数jj,b2一定有约数ii,即
b1=jj*t1
b2=ii*t2
所以,t1=t2,假设t=t1=t2,那么结论是:
z = g*ii*jj*t = (i*j/g)*t
也就是z一定是i和j的最小公倍数的整数倍。这样可以通过计算满足条件的t有多少个来计算集合A
由集合A1∩A2 转移到集合A1∩A2∩A3时,可以发现规律是一样的,因此就有转移和递归的思路。
到这一步,还没完,这样做也会超时,需要继续优化。
为考虑到A1∩A2转移到A1∩A2∩A2只和最小公倍数有关,因此就可以利用最小公倍数作为递归的参数。而最小公倍数有是有限的,a=30 b=10000000时,也只有7000个不重复的最小公倍数,因此可以想到把递归参数哈希+记忆化。
如此一来求集合A的复杂度只有30*7000*2*30最后一个30是递归函数里面的循环参数。
结语:这道题,花了很多时间,最终也没能晋级,不过题目很好。