首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【AcWing】蓝桥杯辅导课-二分与前缀和

  • 25-02-21 20:21
  • 4409
  • 12494
blog.csdn.net

目录

二分

数的范围

数的三次方跟

机器人跳跃问题

四平方和

分巧克力

前缀和

前缀和

子矩阵的和

K倍区间

激光炸弹


二分

数的范围

789. 数的范围 - AcWing题库

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, q, k, a[N];
  5. int main()
  6. {
  7. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  8. cin >> n >> q;
  9. for(int i = 0;i < n;i ++) cin >> a[i];
  10. while(q --)
  11. {
  12. cin >> k;
  13. int l = 0, r = n - 1;
  14. // 先找左端点
  15. while(l < r)
  16. {
  17. int mid = l + r >> 1;
  18. if(a[mid] < k) l = mid + 1;
  19. else r = mid;
  20. }
  21. if(a[l] != k) cout << "-1 -1" << '\n';
  22. else
  23. {
  24. cout << l << ' ';
  25. l = 0, r = n - 1;
  26. while(l < r)
  27. {
  28. int mid = l + r + 1 >> 1;
  29. if(a[mid] > k) r = mid - 1;
  30. else l = mid;
  31. }
  32. cout << r << '\n';
  33. }
  34. }
  35. return 0;
  36. }

数的三次方跟

790. 数的三次方根 - AcWing题库

  1. #include<iostream>
  2. #include<iomanip>
  3. using namespace std;
  4. int main()
  5. {
  6. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  7. double n; cin >> n;
  8. double l = -10000, r = 10000;
  9. while(r - l > 1e-8)
  10. {
  11. double mid = (l + r) / 2;
  12. if(mid * mid * mid < n) l = mid;
  13. else r = mid;
  14. }
  15. cout << fixed << setprecision(6) << l << '\n';
  16. return 0;
  17. }

机器人跳跃问题

730. 机器人跳跃问题 - AcWing题库

这道题是一个求最值的问题,我们在求最值时,通常是二分->dfs->dp->贪心。
二分的重点是看是否有二段性。在很多情况下,二分不仅仅只有二段性,还有单调性,但是没有单调性也可能可以使用二分做,只要有二段性即可,有单调性就一定有二段性。

H(k + 1) > E    ->  E - (H(k + 1) - E) = 2*E - H(k + 1)

H(k + 1) <= E  ->  E + E - H(k + 1)  = 2*E - H(k + 1)

会发现无论当前剩余能量大于、小于或等于下一个建筑的能力值,新能量值的计算方法是一样的 

有了这个计算公式之后。假设起始的最低能力值是E0,也就是说2*E0 - H(k + 1)始终能够大于等于0,若E >= E0,则一定有2*E0 - H(k + 1) >= 0;若E < E0,则一定有2*E0 - H(k + 1) < 0
这样我们就找到了二段性,即这道题可以使用二分来解

如何验证当前的mid是否可行呢?
只需要使用当前的mid去递推一遍,看中间是否有小于0的即可

刚开始二分时,l和r分别要是多少呢?
当数组中全是0时,可以取到初始能量值的最小值,也就是0,所以l从0开始
假设数组中的最大值是hm,当我们当前剩余能力E>=hm时,可以利用放缩
跳到下一个建筑物后剩余能量 = E + E - H(k + 1) >= E + hm - H(k + 1) >= E
也就是说,当某一时刻剩余能量大于等于数组H的最大值时,就不需要计算了,因为往后
2*E - H(k + 1)一定大于等于E,也就是大于等于0。所以,r直接从hm开始即可。

因为h[i]的最大值是10^5,而当前剩余能量E只要大于hm就可以不需要计算,所以2*E一定不会超过int的范围,所以不需要开long long

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int h[N], n, m;
  5. bool check(int mid)
  6. {
  7. for(int i = 1;i <= n;i ++)
  8. {
  9. mid = 2 * mid - h[i];
  10. if(mid >= m) return true; // 当大于数组最大值时返回true
  11. if(mid < 0) return false;
  12. }
  13. return true;
  14. }
  15. int main()
  16. {
  17. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  18. cin >> n;
  19. for(int i = 1;i <= n;i ++)
  20. {
  21. cin >> h[i];
  22. if(h[i] > m) m = h[i];
  23. }
  24. int l = 0, r = m;
  25. while(l < r)
  26. {
  27. int mid = (l + r) / 2;
  28. if(check(mid)) r = mid;
  29. else l = mid + 1;
  30. }
  31. cout << l << '\n';
  32. return 0;
  33. }

四平方和

AcWing 1221. 四平方和 - AcWing

这道题很容易就能想到暴力做法,枚举a,b,c三个数,再直接计算出d,因为n的最大值是5*10^6,所以a,b,c,d的最大值约为2232,时间复杂度是高于10^8,所以不行。

由于时间复杂度是原因,最多只能枚举两个数,此时可以考虑空间换时间。我们先两层for循环枚举出c^2+d^2,然后将c^2+d^2的结果保存起来,完成之后,再来两层for循环枚举a^2+b^2,然后根据目标值n及a^2+b^2的结果,去找是否有对于的c^2+d^2。判断一个数在一个集合中是否出现过,就可以使用哈希或者二分。

注意:一个数的表示方法是不唯一的,这道题要求我们输出字典序最小的一组,在循环过程中,a和b,c和d都是按照字典序最小排的,但是两者的组合不一定是按照字典序的组合来排序的,所以,我们在存储c^2+d^2时,不能只存储平方和,还需要将c和d都存储起来,直接存储成一个结构体即可

二分

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cmath>
  4. using namespace std;
  5. const int N = 2500010;
  6. struct Sum
  7. {
  8. int s, c, d;
  9. bool operator< (const Sum &t)const // 重载,因为二分要对其进行排序
  10. {
  11. if (s != t.s) return s < t.s;
  12. if (c != t.c) return c < t.c;
  13. return d < t.d;
  14. }
  15. }sum[N];
  16. int n, m;
  17. int main()
  18. {
  19. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  20. cin >> n;
  21. for (int c = 0; c * c <= n; c ++ )
  22. for (int d = c; c * c + d * d <= n; d ++ )
  23. sum[m ++ ] = {c * c + d * d, c, d};
  24. sort(sum, sum + m);
  25. for (int a = 0; a * a <= n; a ++ )
  26. for (int b = 0; a * a + b * b <= n; b ++ )
  27. {
  28. int t = n - a * a - b * b;
  29. int l = 0, r = m - 1;
  30. while (l < r) // 查找c^2+d^2中是否有与t相等的
  31. {
  32. int mid = l + r >> 1;
  33. if (sum[mid].s >= t) r = mid;
  34. else l = mid + 1;
  35. }
  36. if (sum[l].s == t)
  37. {
  38. cout << a << ' ' << b << ' ' << sum[l].c << ' ' << sum[l].d << '\n';
  39. return 0;
  40. }
  41. }
  42. return 0;
  43. }

哈希表

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 5e6 + 10;
  4. bool flag[N];
  5. int C[N], D[N];
  6. int n, m;
  7. int main()
  8. {
  9. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  10. cin >> n;
  11. for (int c = 0; c * c <= n; c++)
  12. for (int d = c; c * c + d * d <= n; d++)
  13. {
  14. int t = c * c + d * d;
  15. if (!flag[t])
  16. {
  17. C[t] = c, D[t] = d;
  18. flag[t] = true;
  19. }
  20. }
  21. for (int a = 0; a * a <= n; a++)
  22. for (int b = 0; a * a + b * b <= n; b++)
  23. {
  24. int t = n - a * a - b * b;
  25. if (flag[t])
  26. {
  27. cout << a << ' ' << b << ' ' << C[t] << ' ' << D[t] << '\n';
  28. return 0;
  29. }
  30. }
  31. return 0;
  32. }

分巧克力

1227. 分巧克力 - AcWing题库


这道题是很容易找到二段性的,在1到x之间都是能够切出来的,在x+1到1e5之间是切不出来的,而我们要求的答案就是x,所以此时可以直接使用二分

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, k, h[N], w[N];
  5. bool check(int mid)
  6. {
  7. // 看所有的巧克力中,能否切下k个边长为mid的正方形
  8. int sum = 0;
  9. for(int i = 0;i < n;i ++)
  10. {
  11. sum += (w[i] / mid) * (h[i] / mid);//每一大块可以分成的边长为 mid 的巧克力数量
  12. if (sum >= k) return true;//大于要求数量,返回真
  13. }
  14. return false;
  15. }
  16. int main()
  17. {
  18. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  19. cin >> n >> k;
  20. int l = 1, r = 1e5;
  21. for(int i = 0;i < n;i ++) cin >> h[i] >> w[i];
  22. while(l < r)
  23. {
  24. int mid = l + r + 1 >> 1;
  25. if(check(mid)) l = mid;
  26. else r = mid - 1;
  27. }
  28. cout << l << '\n';
  29. return 0;
  30. }

前缀和

前缀和

795. 前缀和 - AcWing题库

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, m, l, r, a[N], s[N];
  5. int main()
  6. {
  7. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  8. cin >> n >> m;
  9. for(int i = 1;i <= n;i ++) cin >> a[i];
  10. for(int i = 1;i <= n;i ++) s[i] = s[i - 1] + a[i];
  11. while(m --)
  12. {
  13. cin >> l >> r;
  14. cout << s[r] - s[l - 1] << '\n';
  15. }
  16. return 0;
  17. }

子矩阵的和

796. 子矩阵的和 - AcWing题库

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 1010;
  4. int n, m, q, x1, x2, y1, y2, a[N][N], s[N][N];
  5. int main()
  6. {
  7. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  8. cin >> n >> m >> q;
  9. for(int i = 1;i <= n;i ++)
  10. for(int j = 1;j <= m;j ++)
  11. {
  12. cin >> a[i][j];
  13. s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
  14. }
  15. while(q --)
  16. {
  17. cin >> x1 >> y1 >> x2 >> y2;
  18. cout << s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] << '\n';
  19. }
  20. return 0;
  21. }

K倍区间

1230. K倍区间 - AcWing题库

这道题最容易想到的就是直接暴力枚举区间

  1. #include<iostream>
  2. using namespace std;
  3. typedef long long LL;
  4. const int N = 1e5 + 10;
  5. int n, k, a[N];
  6. LL ans;
  7. int main()
  8. {
  9. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  10. cin >> n >> k;
  11. for(int i = 1;i <= n;i ++) cin >> a[i];
  12. for(int r = 1;r <= n;r ++)
  13. for(int l = 1;l <= r;l ++)
  14. {
  15. int t = 0;
  16. for(int i = l;i <= r;i ++) t += a[i];
  17. if(t % k == 0) ans ++;
  18. }
  19. cout << ans << '\n';
  20. return 0;
  21. }

此时时间复杂度是O(N^3),N的最大值是10^5,会超时
此时我们会发现,最里面一层循环是求下标为[l, r]这个区间的和,所以可以使用前缀和来进行优化

  1. #include<iostream>
  2. using namespace std;
  3. typedef long long LL;
  4. const int N = 1e5 + 10;
  5. int n, k;
  6. LL s[N], ans;
  7. int main()
  8. {
  9. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  10. cin >> n >> k;
  11. for(int i = 1;i <= n;i ++)
  12. {
  13. cin >> s[i];
  14. s[i] += s[i - 1];
  15. }
  16. for(int r = 1;r <= n;r ++)
  17. for(int l = 0;l < r;l ++)
  18. {
  19. if((s[r] - s[l]) % k == 0) ans ++;
  20. }
  21. cout << ans << '\n';
  22. return 0;
  23. }

此时时间复杂度优化到了O(N^2),还是不行。
我们看两层循环中,里面的那一层循环,这一层循环的意思就是当区间右端点r固定时,左区间端点为0 - r-1中,有几个s[l] % k的余数与s[r] % k的余数相同的。所以,我们此时可以去掉这一层循环,维护一个cnt数组,cnt[i]表示r之前的前缀和中%k余数为i的数的个数

  1. #include<iostream>
  2. using namespace std;
  3. typedef long long LL;
  4. const int N = 1e5 + 10;
  5. int n, k;
  6. LL s[N], cnt[N], ans;
  7. int main()
  8. {
  9. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  10. cin >> n >> k;
  11. for(int i = 1;i <= n;i ++)
  12. {
  13. cin >> s[i];
  14. s[i] += s[i - 1];
  15. }
  16. cnt[0] = 1; // cnt[0]中存的是s[]中等于0的数的个数 由于s[0] = 0 所以最初等于0的有1个数 所以cnt[0] = 1
  17. for(int i = 1;i <= n;i ++)
  18. {
  19. ans += cnt[s[i] % k];
  20. cnt[s[i] % k] ++;
  21. }
  22. cout << ans << '\n';
  23. return 0;
  24. }

激光炸弹

99. 激光炸弹 - AcWing题库

这道题的思路很简单,就是先计算出前缀和,然后取出R*R的区间,取值的最大值即可。唯一需要注意的就是R可能很大,需要防止数组越界

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 5010;
  4. int cnt, n, r, x, y, w, s[N][N]; // s数组存放的是前缀和
  5. long long ans;
  6. int main()
  7. {
  8. ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  9. cin >> cnt >> r;
  10. if(r > 5001) r = 5001; // r再大是没有意义的,为了防止数组越界,取5001与r的最小值
  11. int n = r, m = r; // n表示的是用到的最大的横坐标与r的较大值,m是纵坐标
  12. while (cnt--)
  13. {
  14. cin >> x >> y >> w;
  15. x++, y++;
  16. if (x > n) n = x;
  17. if (y > m) m = y;
  18. s[x][y] += w;
  19. }
  20. // 计算前缀和
  21. for (int i = 1; i <= n; i++)
  22. for (int j = 1; j <= m; j++)
  23. {
  24. s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
  25. }
  26. // 取前缀和中R*R区间的最大值
  27. for (int i = r; i <= n; i++)
  28. for (int j = r; j <= m; j++)
  29. {
  30. int t = s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r];
  31. if (t > ans) ans = t;
  32. }
  33. cout << ans << '\n';
  34. return 0;
  35. }

注:本文转载自blog.csdn.net的while(77)的文章"https://blog.csdn.net/2301_80277275/article/details/145431127"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

131
学习和成长
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top