HDU 1085 Holding Bin-Laden Captive!
Holding Bin-Laden Captive!
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/32768 K (Java/Others)
“Oh, God! How terrible! ”
Don’t be so afraid, guys. Although he hides in a cave of Hang Zhou, he dares not to go out. Laden is so bored recent years that he fling himself into some math problems, and he said that if anyone can solve his problem, he will give himself up!
Ha-ha! Obviously, Laden is too proud of his intelligence! But, what is his problem?
“Given some Chinese Coins (硬币) (three kinds-- 1, 2, 5), and their number is num_1, num_2 and num_5 respectively, please output the minimum value that you cannot pay with given coins.”
You, super ACMer, should solve the problem easily, and don’t forget to take $25000000 from Bush!
Output
挺有意思的一道题,很常见(我为啥觉得常见嘞?)的一个定义。
给你三种硬币,面值分别为1,2,5。并给出它们各自的数量,请你求出最小的金额,无法用这些硬币的一个子集恰好表示。
这和FJOI的一道题很像,只是比那道题要弱很多,关于FJOI的那道题可以在我的Blog中搜索“福建神题”以查看详情。
那么,这么小的数据范围,做法显然多种多样,先讲蒟蒻(我)的做法好了。
显然这道题好多人给的标签都是生成函数,确实可以套一层生成函数的外壳就是了,但是其代码实现我觉得都用不着搬出生成函数来解释,而且无脑套生成函数会使得你的常数剧增(因为这题其实只是个01问题,好多人写模板都是直接求integer系数)。不过还是说一下。
这题按照题意,母函数就是$F(x)=(1+x+x^{2}+x^{3}+...+x^{k_{1}})(1+x^{2}+x^{4}+x^{6}+...+x^{2k_{2}})(1+x^{5}+x^{10}+x^{15}+...+x^{5k_{3}})=\sum_{i=0}^{k_{1}}{x^{i}}\sum_{i=0}^{k_{2}}{x^{2i}}\sum_{i=0}^{k_{3}}{x^{5i}}$,
然后就直接暴力做多项式乘法(貌似需要一点小小的优化,就是一开始边界不要给到8000什么的)就可以水过了。
然而我们不难看出这题只需要知道能否表示即可,并不需要计算具体的表示方案数,所以我们用一个bitset就可以表示所有系数是0还是其它了,这个不想手写可以用C++的STL,心疼Pascal童鞋一秒。
然后想起来,bitset的卷积,就是bitset的移位运算+或运算,所以还可以用移位运算优化一下,应该能快一些?(数据这么水,我也不知道能不能显现出来啊)
然后又想起来,bitset的卷积,写个优化算法就可以再除个log,那岂不是美滋滋?但是太难写了(偷个懒啦)。
所以贴上我的bitset暴力(母函数)的代码。
1 #include <bitset> 2 #include <cstdio> 3 4 int a, b, c; 5 6 std::bitset<8005> S; 7 8 signed main(void) 9 { 10 while (scanf("%d%d%d", &a, &b, &c), a || b || c) 11 { 12 S.reset(); 13 14 { 15 for (int i = 0; i <= a; ++i) 16 for (int j = 0; j <= b; ++j) 17 S.set(i + j * 2); 18 } 19 20 { 21 int lim = a + b * 2; 22 23 for (int i = lim; i >= 0; --i)if (S[i]) 24 for (int j = c; j >= 0; --j) 25 S.set(i + j * 5); 26 } 27 28 int ans = 1; 29 30 while (S[ans])++ans; 31 32 printf("%d\n", ans); 33 } 34 }
然后来说说机智的做法。
如果当前有一个集合,可以表示$[1,a]$之内的所有数字,那么考虑向其中加入一个元素$b$会怎样。
如果$b\leq a+1$,那么新集合可以表示$[1,a+b]$内的所有数字。
反之,$a+1$将无法被任何子集表示,所以最小的不可表示数字就是$a+1$。
根据这个性质,我们设置$ans=1$,并令其不断逼近答案,每次取出小于等于$ans$的所有数字之和设为$sum$,
如果$sum<ans$,那么ans就是无法被表达的(这是显然的吧,取出来的加一起都够不到$ans$,而没取出来的随便一个都比$ans$大)。
反之,我们就令$ans=sum+1$,继续该过程。
这个算法的复杂度是有保证的。考虑算法如果没有结束,说明$ans=sum+1$,那么下一次取出的数字中一定要有一个在$[lastAns,nowAns]$之间的数字,否则将在下一次取数后结束。而这区间内的一个数字是大于$lastSum$,也就是说取出的集合中的数字之和至少翻番,所以总的取数次数是$O(log(\sum_{a_{i}\in S}{a_{i}}))$的,其中$ai$是给定集合$S$的元素。而我们的取数操作显然是$O(1)$的,所以算法复杂度就是$O(log(\sum_{a_{i}\in S}{a_{i}}))$。
下面给出这个算法的代码实现,在HDU上0MS通过。
1 #include <cstdio> 2 3 int s[3] = {1, 2, 5}, c[3]; 4 5 signed main(void) 6 { 7 while (scanf("%d%d%d", c, c + 1, c + 2), c[0] || c[1] || c[2]) 8 { 9 int ans = 1; 10 11 while (true) 12 { 13 int sum = 0; 14 15 for (int i = 0; i < 3; ++i) 16 if (s[i] <= ans) 17 sum += s[i] * c[i]; 18 19 if (ans > sum) 20 break; 21 else 22 ans = sum + 1; 23 } 24 25 printf("%d\n", ans); 26 } 27 }
@Author: YouSiki