[关闭]
@Dmaxiya 2020-08-25T00:42:16.000000Z 字数 18538 阅读 2209

第一届 Hello World 程序设计竞赛题解

Hello_World


A. 逃脱游戏

题意

给定三个数字,输出三个数中对 取余最大的数字,如果有两个数字对 的余数相等,输出靠前的数字。

数据范围

对于 的数据,
对于 的数据,
对于 的数据,

题解

本题作为全场最简单的题目,由于太简单了,所以我们增大了一些数据范围,使这个问题看起来更不容易拿满分一些。
对于 的数据,几乎所有人都能通过,如果变量类型设为 long long,则可以得到 的分数,如果变量类型设为 unsigned long long,则可以得到 的分数(因为 unsigned long long 的数据范围)。如果要拿满分,就要用字符串来读入了,因为 C/C++ 中没有任何基本数据类型可以直接读入 大小的数字。
对于一个大整数,我们有两种方法求其对 的余数,第一种是利用小学的能被 整除的数字的性质:一个数若各个位上的数字的和是 的倍数则这个数能够被 整除,可以很容易证明:各个位上的数字的和对 的余数,就是这个数字本身对 的余数;第二种方法是利用公式 ,将 位大整数拆分成 ,计算 ,由于当 很大时 无法用基本数据类型储存,所以再用一个乘法分配律化简一下再进行计算,就能得到正确答案。

标程

std1.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 100 + 100
  7. char str[3][maxn];
  8. int MOD(char *str) {
  9. int ret = 0, i;
  10. for(i = 0; str[i]; ++i) {
  11. ret = ret * 10 + str[i] - '0';
  12. ret %= 3;
  13. }
  14. return ret;
  15. }
  16. int main(int argc, char *argv[]) {
  17. freopen("A.in", "r", stdin);
  18. freopen("A.out", "w", stdout);
  19. int mod = -1, ans, i;
  20. for(i = 0; i < 3; ++i) {
  21. scanf("%s", str[i]);
  22. int mtmp = MOD(str[i]);
  23. if(mod < mtmp) {
  24. ans = i;
  25. mod = mtmp;
  26. }
  27. }
  28. printf("%s\n", str[ans]);
  29. fclose(stdin);
  30. fclose(stdout);
  31. return 0;
  32. }

std2.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 100 + 100
  7. char str[3][maxn];
  8. int MOD(char *str) {
  9. int ret = 0, i;
  10. for(i = 0; str[i]; ++i) {
  11. ret += str[i] - '0';
  12. }
  13. return ret % 3;
  14. }
  15. int main(int argc, char *argv[]) {
  16. freopen("A.in", "r", stdin);
  17. freopen("A.out", "w", stdout);
  18. int mod = -1, ans, i;
  19. for(i = 0; i < 3; ++i) {
  20. scanf("%s", str[i]);
  21. int mtmp = MOD(str[i]);
  22. if(mod < mtmp) {
  23. ans = i;
  24. mod = mtmp;
  25. }
  26. }
  27. printf("%s\n", str[ans]);
  28. fclose(stdin);
  29. fclose(stdout);
  30. return 0;
  31. }

B. 卖火柴的小女孩(节选)

题意

给两个正整数 ,如果在区间 内能找到三个互不相等的整数构成一个三角形的三边,则输出 ,并输出三条边的长度,否则输出一个

数据范围

题解

本题作为全场第二简单的题目,可能通过率比第一题还要多一些,唯一的坑就是区间 ,除了这种情况,当 时,总能找到满足题意的答案,三边长度 为其中一个可行解。

特殊判定

特殊判定程序逻辑:

  1. 忽略 大小写进行比对;
  2. 若选手输出第一行字符串与标准输出第一行字符串不同,则返回程序错误;
  3. 若标准输出为 ,则返回程序正确;
  4. 读入选手输出第二行的 个数字,若选手输出不足 个数字,或者输出的数字不在区间 内,则返回程序错误;
  5. 若选手输出小数或其他字符,则会由于读入错误判定为选手输出的数字在 外,返回程序错误,若选手输出的数字多于 个,则对多余的数字不做判定;
  6. 若选手输出的三个数字存在重复,则返回程序错误;
  7. 若选手输出的数字不能构成三角形的三边(即 ),则返回程序错误;
  8. 返回程序正确。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. int main(int argc, char *argv[]) {
  7. freopen("B.in", "r", stdin);
  8. freopen("B.out", "w", stdout);
  9. int l, r;
  10. int a, b, c;
  11. scanf("%d%d", &l, &r);
  12. if(r - l >= 2) {
  13. a = r - 2;
  14. b = r - 1;
  15. c = r;
  16. if(a + b > c) {
  17. printf("YES\n%d %d %d\n", a, b, c);
  18. } else {
  19. printf("NO\n");
  20. }
  21. } else {
  22. printf("NO\n");
  23. }
  24. fclose(stdin);
  25. fclose(stdout);
  26. return 0;
  27. }

C. 深井中的蜗牛

题意

一只蜗牛从深为 米的井底往井口爬,白天往上爬 米,晚上往下滑 米,输出蜗牛第几天爬出枯井。

数据范围

题解

本题作为全场第三简单的题目,看上去可能要特判很多种情况,比较好判断的有:,这种情况只要它第一天爬不到井口,就永远都爬不到了,还有到哪一天正好爬到井口,晚上就不再下滑了,这些细节上的处理可能就比较烦了。
其实看到这题的数据范围就可以发现:如果蜗牛能够爬到井口,它最多只需要 天( 都为整数,在 的情况中,,所以 ),所以直接按题意模拟,最多模拟 次就可以知道蜗牛第几天能爬出枯井,或者永远不能爬出枯井(超出 次还没有爬到枯井就说明蜗牛永远都爬不出枯井)。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. int main(int argc, char *argv[]) {
  7. freopen("C.in", "r", stdin);
  8. freopen("C.out", "w", stdout);
  9. int a, b, x;
  10. int ans, top;
  11. top = ans = 0;
  12. scanf("%d%d%d", &x, &a, &b);
  13. while(top < x) {
  14. ++ans;
  15. top += a;
  16. if(top >= x) {
  17. break;
  18. }
  19. top -= b;
  20. if(ans > 200) {
  21. break;
  22. }
  23. }
  24. if(ans > 200) {
  25. printf("impossible\n");
  26. } else {
  27. printf("%d\n", ans);
  28. }
  29. fclose(stdin);
  30. fclose(stdout);
  31. return 0;
  32. }

D. 修路工程

题意

在一张 个节点的完全图(任意两个节点之间都有一条连边的简单图, 个节点的完全图上有 条边)上,求其最小生成树的边权和,第 个节点的点权为 ,则点 与点 之间的边权为

数据范围

对于 的数据,
对于 的数据,
对于 的数据,

题解

作为全场的中等难度中最简单的题目,为了让题目看上去不显得那么 ,我们特地为大家设计了一个图论背景,还考了最小生成树算法(不管你们看不看得出来,反正它是一个最小生成树),我们又懒得解释“完全图”和“最小生成树”这些要用一大段专业术语来解释的名词,就给出了这样的以“修路”为背景的题面……


好,废话到此为止
如果有同学会 (普里姆求最小生成树)算法的话,恭喜你,可以通过 的数据,可以先构造出 条边的一张完全图,然后在图上跑一遍 ,由于 算法的时间复杂度为 ,其中 为节点数,所以对于最后 组数据(),要构造出所有的边,计算机最少的计算次数为 ,远远超过 ,程序超时是必然的。


好,上面的都是废话,下面的才是真正的题解。
我们知道,最小生成树除了 算法还有一个名字更长的 (克鲁斯卡尔)算法,这种算法的做法是将所有边的长度按照从小到大的顺序排序,依次取边,如果取到的边的两个端点之间没有路径可以互相到达,就选择这条边连接两个端点,作为生成树上的一条边,否则忽略这条边继续往后取边。
在这题当中我们不必处理出所有的边,因为我们可以发现所有按照以上方法取出的边必然是点权最小的点 与其他所有节点的连边,所以最后最小生成树上的每条边一定与点 相连,所以最小生成树的权值和就是 。这种做法的时间复杂度为 ,对于最大的一组数据,其计算次数仅为 ,小于 ,可以在 内运行出结果。
所以说了那么多,这题的代码其实只有一个 for 循环就完事了,或许不需要会什么 ,不需要知道最小生成树,猜结论都可以猜出来,由于这题的代码太简单了,所以实际上并没有什么难度,想到了就能写出来。
最后,由于后 组数据比较大,所以直接用 cin cout 会导致程序读入还没结束就已经超时,本题的读入已经在选手须知的“友情提示”中提醒大家了。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 1000000 + 100
  7. int n;
  8. long long sum, Min;
  9. long long num[maxn];
  10. long long min(long long a, long long b) {
  11. return a < b? a: b;
  12. }
  13. int main(int argc, char *argv[]) {
  14. freopen("D.in", "r", stdin);
  15. freopen("D.out", "w", stdout);
  16. int i;
  17. Min = maxn;
  18. scanf("%d", &n);
  19. for(i = 0; i < n; ++i) {
  20. scanf("%lld", &num[i]);
  21. Min = min(Min, num[i]);
  22. sum += num[i];
  23. }
  24. sum += Min * (n - 2);
  25. printf("%lld\n", sum);
  26. fclose(stdin);
  27. fclose(stdout);
  28. return 0;
  29. }

E. 算积分

题意

计算以下积分:


输出保留两位小数。

数据范围


题解


的原函数不是初等函数
题给积分上下限是 ,而不是
又说了四行废话
这题最重要的是数据范围,,所以可以用积分的定义 越小,则结果越精确,而且输出只要保留两位小数,所以只要设置好 的范围使得 ,就能通过程序。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. double getf(double x) {
  7. return exp(-x * x) + sin(x) / x + 1 / log(x);
  8. }
  9. int main(int argc, char *argv[]) {
  10. freopen("E.in", "r", stdin);
  11. freopen("E.out", "w", stdout);
  12. int T, i;
  13. double ii;
  14. scanf("%d", &T);
  15. for(i = 0; i < T; ++i) {
  16. double a, b, S = 0;
  17. scanf("%lf%lf", &a, &b);
  18. for(ii = a; ii < b; ii += 0.0001) {
  19. S += getf(ii) * 0.0001;
  20. }
  21. printf("%.2lf\n", S);
  22. }
  23. fclose(stdin);
  24. fclose(stdout);
  25. return 0;
  26. }

F. 解密

题意

输出将 个数全排列按字典序排序后,排列 往后数 个的排列。

数据范围

题解

这题想说废话也没得说了,直接按题意先暴力跑到排列 ,接着往后面跑 个排列,每往后跑一个排列则 --k,如果所有排列都已经跑完而 ,则输出 "impossible"。递归大暴力是最好想但不是最好写的方法。
这题思维上没什么难度,主要就是一个递归函数要花一些时间写一下,比较考递归函数的代码实现能力。当然如果会调用 C++ 的函数 next_permutation,或者找到类似字典序法生成下一个全排列的规律,那这题一个 while 循环就解决了。
这题如果 的值设置得更大一些(例如 ),而 的值小一些,就需要用到康拓展开及其逆运算的知识了,学习理解康托展开之前建议先理解 进制这种数的表示方法,本题由于 太大,康拓展开需要进行大数运算,所以无法用康拓展开,但建议了解一下。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 10000 + 100
  7. int n, k;
  8. int num[maxn];
  9. int vis[maxn], ans[maxn];
  10. void dfs(int depth, int limit) {
  11. if(depth == n + 1) {
  12. --k;
  13. return ;
  14. }
  15. int i, low;
  16. if(limit == 0) {
  17. low = 1;
  18. } else {
  19. low = num[depth];
  20. }
  21. for(i = low; i <= n; ++i) {
  22. if(vis[i] == 0) {
  23. vis[i] = 1;
  24. ans[depth] = i;
  25. dfs(depth + 1, limit && i == low);
  26. vis[i] = 0;
  27. if(k == 0) {
  28. return ;
  29. }
  30. }
  31. }
  32. }
  33. int main(int argc, char *argv[]) {
  34. freopen("F.in", "r", stdin);
  35. freopen("F.out", "w", stdout);
  36. int i;
  37. scanf("%d%d", &n, &k);
  38. ++k;
  39. for(i = 1; i <= n; ++i) {
  40. vis[i] = 0;
  41. scanf("%d", &num[i]);
  42. }
  43. dfs(1, 1);
  44. if(k == 0) {
  45. for(i = 1; i <= n; ++i) {
  46. if(i != 1) {
  47. printf(" ");
  48. }
  49. printf("%d", ans[i]);
  50. }
  51. printf("\n");
  52. } else {
  53. printf("impossible\n");
  54. }
  55. fclose(stdin);
  56. fclose(stdout);
  57. return 0;
  58. }

G. 一键复制粘贴

题意

有一个空字符串,对于这个空字符串有两种操作,第一种操作是往串尾添加一个字符,第二种操作是按下 Ctrl 键进行一键全选复制粘贴,且第一种操作可以进行无限次而第二种操作只能进行一次,要求用最少的操作次数得到字符串 ,输出最少的操作次数。

数据范围

对于 的数据,
对于 的数据,
对于 的数据,

题解

啊……三道基础题和三道假算法题之后,终于来到了算法题部分,这题并不是由于它是高分题中最简单的而被放在第一题,实际上“香槟酒”更简单,只是因为,如果想不出“香槟酒”的正解,基本上拿不到分数,而这题就算只想出纯暴力的方法,也能拿到 的分数,所以这题的数据相比于后面一题,更加友好一些。
实际上我们第一次出的 组数据,由于没考虑到另一种优美的暴力写法,导致优美的暴力能够通过所有的数据,所以我们加强了第 组到第 组的数据,不论多优美的暴力都会超时的数据(flag 先立在这了,出数据的我坐等打脸)。


稍微往后面想两步就可以发现,这题要求的是:将字符串 拆分成 这种形式的字符串,且要使字符串 的长度最大,能通过 的暴力解法,就是从 枚举字符串 的长度 ,判断 的子串 是否等于 ,取满足条件的最长的 。因为这种做法要枚举 ,且对于每个 ,都需要进行 次比较,所以总的程序执行次数为:(如果数据足够随机,这个比较次数将会是很小的数字,所以我们人为地设计了数据),对于 的数据,计算次数最多为 ,足以通过 的数据,而对 的数据 ,最少计算次数约为 ,超时稳的。


那现在来说一下正解吧,本题的正解有两种,会用其中任意一种就可以过题,这题的数据没什么坑,只要写出来过了样例基本上就能得满分。一种是字符串匹配 算法,另一种是字符串哈希。
在看题解之前先了解一下 吧:字符串练习题解


好,理解了 ,我们就可以用第一种解法来解决这个问题了。
数组的第 位存的是第 个字符及前面所有字符所组成的字符串的最长相同前后缀对应的前缀下标。我们可以发现,在 的情况下, 所代表的前缀就是一个最小周期长度为 的周期为 的周期串,因为最小周期长度为 的字符串同时也是一个周期长度为 的周期串,因此只要 能整除 ,那么 对应的前缀字符串就可以拆分成两个完全相同的字符串。于是就可以在字符串 跑出 数组后,检查满足条件 的所有 中,最大的 值,答案就是
下面的 std1.c 代码就是用 算法解决的,也是代码长度最短的解法。


另一种字符串哈希的方法,实际上相比于纯暴力解法,就是加快了比较两个字符串是否相等的速度,在暴力解法中,比较两个长度为 的字符串是否相等的比较次数最大为 次,通过字符串哈希可以只通过一次比较就判断出两个字符串是否相等。
那再来简单解释一下字符串哈希吧,字符串哈希就是相当于把一个字符串看作一个 进制的数字(看字符集的大小,如果既有大写字母又有小写字母,且区分大小写的话,字符集大小就是 ,要哈希这个字符串就要用 进制),第 位的位权为 ,第 位的位权为 ,以此类推,第 位的位权为 。由于基本数据类型无法存下 大小的数字,所以我们可以用 A 题中的方法,取一个大质数,将字符串哈希后的值对这个大质数取余(大质数一般取 ),这样在能保证按照一定规则对字符串进行处理后,得到一个唯一确定的值,相同的字符串哈希后的值就是相等的,不同字符串哈希后的值相等的概率极小,发生冲突的概率为 为哈希字符串所选择的质数。为了减小哈希的冲突概率,我们可以用双哈希的方法,也就是用两个大质数来对同一个字符串进行哈希,只有当两个哈希值都相等的时候,才认为两个字符串相等,这样哈希冲突的概率就可以降到 本题的 std2.cstd3.c 用的都是双哈希。
这里以十进制为例,说明如何用哈希的方式比较两个字符串是否相等。例如对于数字:(假设字符集为 ,将第 个字符的位权设为 ,那么哈希后的字符串在形式上与对应的十进制数是相反的),我们用一个 数组来记录每一位的 值,于是就有以下数组(数组下标从 开始,而字符串下标从 开始):

0 1 2 3 4 5 6
0 1 21 321 1321 21321 321321

从这个数组中截取原串的第 位到第 位子串对应的哈希值的计算方式为:(可以很容易证明 一定是 的整数倍)。
这个字符串中出现了两次 子串,我们可以取出这个字符串的第 到第 位子串对应的哈希值,取法:,将得到的数字与第 到第 位子串对应的哈希值()进行比较,发现 ,因此原字符串内的子串
现在将十进制转化到 进制,则获取子串 的哈希值方式为:,由于哈希值的获取涉及取模运算,所以不能直接对数字进行除法操作,需要求出除数相对于模数的逆元(详见 求逆元),这也是哈希模数取质数的原因。
在取模运算中运用除法,是标程 std3.c 中的做法,而 std2.c 中的做法,则不运用除法,而是直接将低位的数字乘上 次方后与高位的数字进行对比,即判断 是否等于 ,在取模运算中涉及到乘法,可以直接运用与加法类似的公式:,进行求解,而不需要用到逆元的知识。

标程

std1.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 1000000 + 100
  7. int ans, len;
  8. int Next[maxn];
  9. char str[maxn];
  10. int min(int a, int b) {
  11. return a < b? a: b;
  12. }
  13. void get_Next(char *str) {
  14. int j = 0, i;
  15. len = strlen(str + 1);
  16. Next[1] = 0;
  17. for(i = 2; i <= len; ++i) {
  18. while(j != 0 && str[j + 1] != str[i]) {
  19. j = Next[j];
  20. }
  21. if(str[j + 1] == str[i]) {
  22. ++j;
  23. }
  24. Next[i] = j;
  25. }
  26. }
  27. int main(int argc, char *argv[]) {
  28. freopen("G.in", "r", stdin);
  29. freopen("G.out", "w", stdout);
  30. int i;
  31. scanf("%s", str + 1);
  32. get_Next(str);
  33. ans = len;
  34. for(i = 2; i <= len; i += 2) {
  35. if((i / 2) % (i - Next[i]) == 0) {
  36. ans = min(ans, i / 2 + 1 + len - i);
  37. }
  38. }
  39. printf("%d\n", ans);
  40. fclose(stdin);
  41. fclose(stdout);
  42. return 0;
  43. }

std2.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 1000000 + 100
  7. int len, ans;
  8. char str[maxn];
  9. long long Pow[2][maxn];
  10. long long Sum[2][maxn];
  11. long long MOD[2] = {1000000007, 10000009};
  12. void Init() {
  13. int i, j;
  14. Pow[0][0] = Pow[1][0] = 1;
  15. for(i = 0; i < 2; ++i) {
  16. for(j = 1; j < maxn; ++j) {
  17. Pow[i][j] = Pow[i][j - 1] * 26 % MOD[i];
  18. }
  19. }
  20. }
  21. void get(char *str) {
  22. int i, j;
  23. for(i = 0; i < 2; ++i) {
  24. Sum[i][0] = 0;
  25. for(j = 1; str[j]; ++j) {
  26. Sum[i][j] = (Sum[i][j - 1] + (str[j] - 'a' + 1) * Pow[i][j] % MOD[i]) % MOD[i];
  27. }
  28. }
  29. }
  30. int judge(int Index) {
  31. int i;
  32. for(i = 0; i < 2; ++i) {
  33. long long num1 = ((Sum[i][Index] - Sum[i][Index / 2]) % MOD[i] + MOD[i]) % MOD[i];
  34. long long num2 = Sum[i][Index / 2] * Pow[i][Index / 2] % MOD[i];
  35. if(num1 != num2) {
  36. return 0;
  37. }
  38. }
  39. return 1;
  40. }
  41. int min(int a, int b) {
  42. return a < b? a: b;
  43. }
  44. int main(int argc, char *argv[]) {
  45. freopen("G.in", "r", stdin);
  46. freopen("G.out", "w", stdout);
  47. int i;
  48. Init();
  49. scanf("%s", str + 1);
  50. get(str);
  51. ans = len = strlen(str + 1);
  52. for(i = 2; i <= len; i += 2) {
  53. if(judge(i)) {
  54. ans = min(ans, i / 2 + 1 + len - i);
  55. }
  56. }
  57. printf("%d\n", ans);
  58. fclose(stdin);
  59. fclose(stdout);
  60. return 0;
  61. }

std3.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 1000000 + 100
  7. char s[maxn];
  8. long long inv[2];
  9. long long sum[2][maxn];
  10. long long Pow[2][maxn];
  11. long long invpow[2][maxn];
  12. long long mod[2] = {1000000007, 1000000009};
  13. void exgcd(long long a, long long b, long long *x, long long *y) {
  14. if(b == 0) {
  15. *x = 1;
  16. *y = 0;
  17. return ;
  18. }
  19. exgcd(b, a % b, y, x);
  20. *y -= *x * (a / b);
  21. }
  22. long long ch(char u) {
  23. return u - 'a' + 1;
  24. }
  25. long long getInv(long long num, long long mod) {
  26. long long x, y;
  27. exgcd(num, mod, &x, &y);
  28. return (x % mod + mod) % mod;
  29. }
  30. void Init(char *str, int len) {
  31. int i, j;
  32. long long x, y;
  33. for(i = 0; i < 2; ++i) {
  34. Pow[i][0] = 1;
  35. inv[i] = getInv(26, mod[i]);
  36. invpow[i][0] = 1;
  37. sum[i][0] = 1;
  38. }
  39. for(i = 0; i < 2; ++i) {
  40. for(j = 1; j < maxn; ++j) {
  41. Pow[i][j] = Pow[i][j - 1] * 26 % mod[i];
  42. invpow[i][j] = (invpow[i][j - 1] * inv[i]) % mod[i];
  43. }
  44. }
  45. for(i = 0; i < 2; ++i) {
  46. for(j = 1; j <= len; ++j) {
  47. sum[i][j] = (sum[i][j - 1] * 26 + ch(str[j])) % mod[i];
  48. }
  49. }
  50. }
  51. long long get(char *str, int l, int r, int Index) {
  52. return (((sum[Index][r] - sum[Index][l - 1]) % mod[Index]) + mod[Index]) % mod[Index] * invpow[Index][l] % mod[Index];
  53. }
  54. int min(int a, int b) {
  55. return a < b? a: b;
  56. }
  57. int main(int argc, char *argv[]) {
  58. freopen("G.in", "r", stdin);
  59. freopen("G.out", "w", stdout);
  60. int i, len, ans, Len;
  61. scanf("%s", s + 1);
  62. len = strlen(s + 1);
  63. Init(s, len);
  64. ans = len;
  65. for(i = 1; i <= len; ++i) {
  66. Len = i;
  67. if(i + Len <= len) {
  68. if((get(s, 1, i, 0) == get(s, i + 1, i + Len, 0)) && get(s, 1, i, 1) == get(s, i + 1, i + Len, 1)) {
  69. ans = min(ans, 1 + len - Len);
  70. }
  71. }
  72. }
  73. printf("%d\n", ans);
  74. fclose(stdin);
  75. fclose(stdout);
  76. return 0;
  77. }

H. 香槟酒

题意

给一种香槟酒的倒酒规则,问要把第 行的第 个酒杯装满,至少要倒多少单位体积的香槟酒。

数据范围

题解

这题数据范围本来出 的,后来我们我们发现要倒满第 层的酒杯的答案大小远远超出了 double 能够表示的范围,就改成了 了。
我们稍微往第三层第四层画下去几层可以发现,在同一层内,要装满两端的两个酒杯所需要的香槟酒是最多的,由于两端的情况比较好推,所以我们先从这里入手来看看有什么规律。通过找规律或者简单证明一下可以发现:装满第 层两端的酒杯需要的香槟酒为 ,所以如果把数据范围设为 ,那 的答案就是 ,显然 double 是无法存在下这么大的答案的,所以我们将数据范围减小到了 。我们再将这个思路推到更普遍的情况可以发现,这很难推,所以这段话只是说明了我们改数据范围的原因。


我们很容易可以发现,如果我已经知道要倒多少香槟酒到顶端的酒杯,可以 地计算出酒杯 最终含有的香槟酒的体积(用一个二维数组保存对应位置酒杯的酒的体积,模拟一遍倒酒的过程即可)。
并且:往顶端的酒杯倒越多的香槟酒,酒杯 内香槟酒的体积就越大,如果把香槟酒的总体积作为自变量,酒杯 内的香槟酒体积作为函数值,就是一个单调递增的函数,因此我们可以用高中学过的二分法求零点的方式来求函数值等于 的自变量的值。

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 100 + 100
  7. int a, b;
  8. double num[maxn][maxn];
  9. int judge(double x) {
  10. int i, j;
  11. num[1][1] = x;
  12. for(i = 2; i <= a; ++i) {
  13. for(j = 1; j <= i; ++j) {
  14. num[i][j] = 0;
  15. if(num[i - 1][j - 1] >= 1) {
  16. num[i][j] += (num[i - 1][j - 1] - 1) / 2;
  17. }
  18. if(num[i - 1][j] >= 1) {
  19. num[i][j] += (num[i - 1][j] - 1) / 2;
  20. }
  21. }
  22. }
  23. return num[a][b] >= 1;
  24. }
  25. int main(int argc, char *argv[]) {
  26. freopen("H.in", "r", stdin);
  27. freopen("H.out", "w", stdout);
  28. int i;
  29. double high = 1e18;
  30. double low = 0;
  31. double mid;
  32. scanf("%d%d", &a, &b);
  33. for(i = 0; i < 1000; ++i) {
  34. mid = (low + high) / 2;
  35. if(judge(mid) == 1) {
  36. high = mid;
  37. } else {
  38. low = mid;
  39. }
  40. }
  41. printf("%.10f\n", high);
  42. fclose(stdin);
  43. fclose(stdout);
  44. return 0;
  45. }

I. 送分题 A+B

题意

输入 ,输出 的结果。

数据范围

对于 的数据,
对于 的数据,
对于 的数据,

题解

这场比赛原本是 而不是 ,但是我们赛前两周的一次讨论即将结束的时候,许子涵学长提了一个问题,我们给的这些标程代码长度最长的有多长?我看了一下,空行头文件都算进去的话大概五六十行吧……才五六十行啊……我们害怕由于代码太短有大佬很早就结束了比赛提前离场(抱歉,代码短不代表题目就简单),所以打算出一道每个人都知道怎么写,但是写起来代码量大一些的题目,本来打算出高精度整数乘法的,但是其实高精度整数就算加上正负号也就五六十行,两个 for 循环就完事了,所以我和孙昊哲又讨论了怎么出这道题,于是“送分题 A+B” 就这样诞生了——这题本人在有实现高精度整数加减乘除法经验的前提下完成代码,大概写了一个小时的标程,总代码长度 行(当然为了程序逻辑清晰更容易看懂,在前面写了许多函数声明导致代码长度增加,但是只要你的代码风格是正常的,不刻意压行的话, 行代码是少不了的)。算上这道题,本人可能三小时也很难做出分数最高的六题。
这题就是一个高精度整数的加减法,不论输入用的是什么基本数据类型,提交得分都是 ,即使是第一组数据……第一组数据的数据范围其实是用来迷惑你们的,虽然 unsigned long long 的数据范围大约是 ,但是两个 相加就会溢出,为了让你们认真写高精度整数加减法,第一组数据就是两个 。(嘿嘿嘿……)
如果只会写加法不会写减法,也可以获得 的分数,因为那部分数据是 同号的。
同号加法就不说怎么写了,这里主要说一下关于减法与负号如何处理,处理同号加法不是这题的难点,这题的精髓在于处理不同号数据的加减法,这里简单说明一下这部分程序的逻辑:

  1. 首先实现四个基本函数:取相反数,绝对值比较函数,同号加法函数,同号减法函数(在 的情况下)
  2. 的情况下
    1. 为负号,则对 取相反数后调用
    2. 为负号,则对 取相反数后调用
    3. 直接执行
  3. 的情况下:
    1. 为负号,则对 取相反数后调用
    2. 为负号,则对 取相反数后调用 ,再对计算结果取反
    3. ,则调用 ,再对计算结果取反
    4. 直接执行

两种情况下的每一个判断,前面的判断优先级大于后面的判断,也就是如果先满足了前面的判断,就不必执行后面的判断,例如:要进行 的判断,应该先不满足条件 ,也就是 都大于等于 的情况下才会判断 是否为真,因此这里的比较一定是绝对值比较,在执行 时,应该先不满足前面三个条件,也就是在调用 时,已经满足了 非负数且 ,得到的结果必然是一个正数。
这题十分考代码能力与程序的结构化能力,如果对正负号与加减号进行组合判断的话,总共就要写 种情况的运算,代码量将会远远超过 行,且不易

标程

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <math.h>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. #define maxn 100000 + 100
  7. typedef struct BigInteger {
  8. int sign;
  9. int len;
  10. int num[maxn];
  11. } BigInteger;
  12. int max(int a, int b);
  13. void Init(BigInteger *x);
  14. void str2num(char *str, BigInteger *x);
  15. void Copy(BigInteger *from, BigInteger *to);
  16. void CutLeadingZeros(BigInteger *x);
  17. void Get_neg(BigInteger *x);
  18. int smaller(BigInteger *a, BigInteger *b);
  19. void add(BigInteger *a, BigInteger *b, BigInteger *ans);
  20. void sub(BigInteger *a, BigInteger *b, BigInteger *ans);
  21. void Print(BigInteger *x);
  22. char str[maxn];
  23. BigInteger num[2];
  24. BigInteger tmp[2];
  25. BigInteger ans;
  26. int main(int argc, char *argv[]) {
  27. freopen("I.in", "r", stdin);
  28. freopen("I.out", "w", stdout);
  29. int i, j;
  30. for(i = 0; i < 2; ++i) {
  31. scanf("%s", str);
  32. Init(num + i);
  33. str2num(str, num + i);
  34. }
  35. for(i = 0; i < 2; ++i) {
  36. Copy(num + i, tmp + i);
  37. }
  38. Init(&ans);
  39. add(tmp + 0, tmp + 1, &ans);
  40. Print(&ans);
  41. Init(&ans);
  42. sub(num + 0, num + 1, &ans);
  43. Print(&ans);
  44. fclose(stdin);
  45. fclose(stdout);
  46. return 0;
  47. }
  48. int max(int a, int b) {
  49. return a > b? a: b;
  50. }
  51. void Init(BigInteger *x) {
  52. x->len = 1;
  53. x->sign = 1;
  54. int i;
  55. for(i = 0; i < maxn; ++i) {
  56. x->num[i] = 0;
  57. }
  58. }
  59. void str2num(char *str, BigInteger *x) {
  60. int i;
  61. int Left = 0;
  62. int len = strlen(str);
  63. if(str[0] == '-') {
  64. x->sign = 0;
  65. Left = 1;
  66. }
  67. x->len = len - Left;
  68. for(i = len - 1; i >= Left; --i) {
  69. x->num[len - 1 - i] = str[i] - '0';
  70. }
  71. }
  72. void Copy(BigInteger *from, BigInteger *to) {
  73. int i;
  74. to->len = from->len;
  75. to->sign = from->sign;
  76. for(i = 0; i < maxn; ++i) {
  77. to->num[i] = from->num[i];
  78. }
  79. }
  80. void CutLeadingZeros(BigInteger *x) {
  81. int i;
  82. for(i = maxn - 1; i >= 0; --i) {
  83. if(x->num[i] != 0) {
  84. x->len = i + 1;
  85. return ;
  86. }
  87. }
  88. x->len = 1;
  89. x->sign = 1;
  90. }
  91. void Get_neg(BigInteger *x) {
  92. x->sign = 1 - x->sign;
  93. }
  94. int smaller(BigInteger *a, BigInteger *b) {
  95. if(a->len != b->len) {
  96. return a->len < b->len;
  97. }
  98. int i;
  99. for(i = a->len - 1; i >= 0; --i) {
  100. if(a->num[i] != b->num[i]) {
  101. return a->num[i] < b->num[i];
  102. }
  103. }
  104. return 0;
  105. }
  106. void add(BigInteger *a, BigInteger *b, BigInteger *ans) {
  107. if(b->sign == 0) {
  108. Get_neg(b);
  109. sub(a, b, ans);
  110. return ;
  111. }
  112. if(a->sign == 0) {
  113. Get_neg(a);
  114. sub(b, a, ans);
  115. return ;
  116. }
  117. int i, c = 0;
  118. int len = max(a->len, b->len);
  119. for(i = 0; i < len; ++i) {
  120. ans->num[i] = (c + a->num[i] + b->num[i]) % 10;
  121. c = (c + a->num[i] + b->num[i]) / 10;
  122. }
  123. ans->num[len] = c;
  124. ans->sign = 1;
  125. CutLeadingZeros(ans);
  126. }
  127. void sub(BigInteger *a, BigInteger *b, BigInteger *ans) {
  128. if(b->sign == 0) {
  129. Get_neg(b);
  130. add(a, b, ans);
  131. return ;
  132. }
  133. if(a->sign == 0) {
  134. Get_neg(a);
  135. add(a, b, ans);
  136. Get_neg(ans);
  137. return ;
  138. }
  139. if(smaller(a, b)) {
  140. sub(b, a, ans);
  141. Get_neg(ans);
  142. return ;
  143. }
  144. int i, c = 0;
  145. for(i = 0; i < a->len; ++i) {
  146. if(a->num[i] - c < b->num[i]) {
  147. ans->num[i] = a->num[i] - c + 10 - b->num[i];
  148. c = 1;
  149. } else {
  150. ans->num[i] = a->num[i] - c - b->num[i];
  151. c = 0;
  152. }
  153. }
  154. ans->sign = 1;
  155. CutLeadingZeros(ans);
  156. }
  157. void Print(BigInteger *x) {
  158. if(x->sign == 0) {
  159. printf("-");
  160. }
  161. int i;
  162. for(i = x->len - 1; i >= 0; --i) {
  163. printf("%d", x->num[i]);
  164. }
  165. printf("\n");
  166. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注