数的范围
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
思路:
1.找到左端点:
- 区间[0 , n-1]
- 判断条件:num[mid] >= tar
2.找到右端点:
- 区间[左端点, n-1]
- 判断条件:num[mid] <= tar
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 100010; int n,q; int num[N]; int main(void) { scanf("%d %d",&n,&q); for(int i = 0;i < n;i++) scanf("%d",&num[i]); while(q--) { int tar; scanf("%d",&tar); //找左端点 int l = 0,r = n-1; while(l < r) { int mid = l + r >> 1; if(num[mid] >= tar) r = mid; else l = mid + 1; } if(num[l] != tar) printf("-1 -1\n"); else { printf("%d ",l); //找右端点 r = n - 1; while(l < r) { int mid = l + r + 1 >> 1; if(num[mid] <= tar) l = mid; else r = mid - 1; } printf("%d\n",l); } } return 0; }
机器人跳跃问题
机器人正在玩一个古老的基于DOS的游戏。
游戏中有N+1座建筑——从0到N编号,从左到右排列。
编号为0的建筑高度为0个单位,编号为 i 的建筑高度为H(i)个单位。
起初,机器人在编号为0的建筑处。
每一步,它跳到下一个(右边)建筑。
假设机器人在第k个建筑,且它现在的能量值是E,下一步它将跳到第k+1个建筑。
如果H(k+1)>E,那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值。
游戏目标是到达第N个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?
输入格式
第一行输入整数N。
第二行是N个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值。
数据范围
\(1≤N,H(i)≤10^5\)
\(1≤N,H(i)≤10^5\)
思路:
1.分析题目
- 如果机器人具有的能量数是所有建筑高度的MAX,那么机器人一定可以通过所有建筑,因为对于建筑高度小于能量数的,能量数会增加,对于建筑高度等于能量数,能量数会增加0。
- 且机器人具有的最小能量数是应该是1,因为建筑最低高度是1
2.二分法
- 区间[1 , max]
- 判断条件 cal(mid) >= 0,即对于一个能量值mid,定义一个函数cal(int x)进行判断,如果在通过所有建筑后还剩能量大于0,说明初始能量值可能还可以继续缩小。
代码
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 100010; int n; int num[N]; int cal(int x,int ma) { for(int i = 1; i <= n;i++) { if(x >= num[i]) x += (x - num[i]); else x -= (num[i] - x); if(x < 0 || x >= ma) //如果超过max,则一定可以通过;如果小于0,则一定不行 break; } return x; } int main(void) { scanf("%d",&n); int ma = 0; for(int i = 1 ;i <= n; i++ ) { scanf("%d", &num[i]); ma = max(ma,num[i]); } //二分法 int l = 1,r = ma; while(l < r) { int mid = (l + r) >> 1; if(cal(mid,ma) >= 0) r = mid; else l = mid + 1; } printf("%d",r); return 0; }
我遇到的坑
判断函数cal()中一开始是没有
if(x < 0 || x >= ma) break;
数据会导致int x溢出,x累加后的范围确实不好想;我使用long long x结果也溢出了。最后才想到加上上面的判断条件就好了,既快也不会溢出。
分巧克力
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出
输出切出的正方形巧克力最大可能的边长。
思路
和前面那道题很类似了,巧克力大小的区间范围是[1,max],对区间内巧克力的边长进行二分,如果
判断条件cal(mid) >= k,即可以分出边长为mid的巧克力大于k个小朋友,那么就可能可以把巧克力在分的更大一些。
代码
#include <cstring> #include <cstdio> #include <iostream> #include <algorithm> #include <climits> using namespace std; typedef long long LL; const int N = 100010; int num[N][2]; int n,k; LL cal(int dat) { LL sum = 0; for(int i = 0; i < n;i++) { int x = num[i][0]/dat; int y = num[i][1]/dat; sum += x * y; if(sum >= k) break; } return sum; } int main(void) { scanf("%d %d", &n,&k); int ma = 0; for(int i = 0; i < n;i++) { scanf("%d %d",&num[i][0],&num[i][1]); ma = max(ma,min(num[i][0], num[i][1]));//取所有巧克力中宽的最大值 } int l = 1,r = ma; while(l < r) { int mid = (l + r + 1) >> 1; if(cal(mid) >= k) l = mid; else r = mid - 1; } printf("%d",l); return 0; }
遇到的坑
一开始边界区间没有找准,我认为是右边应该是所有巧克力边长中的最小值,因为如果要使用每一块巧克力,那么肯定要找所有边长的最小值,但是事实上,一些某一边长小的巧克力完全可以不要嘛。这个是提交后发现的,还是考虑不周全。
四平方和
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多4个正整数的平方和。
如果把0包括进去,就正好可以表示为4个数的平方和。
比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)
对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对4个数排序:
0 <= a <= b <= c <= d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000)
要求输出4个非负整数,按从小到大排序,中间用空格分开
思路:
我一开始就往二分去想,没有想出来,稍微想了一下枚举,但是感觉会超时,就看了题解,如下:
1.先计算枚举的时间复杂度:\(\sqrt{5*10^6} = 2240\)
估算三重for循环的时间复杂度是O(\(10^9\)),显然超时了(实际上,因为大部分的N的a,b都很小,大部分数据是不会超时的);
2.所以要改进枚举,一般的方法是 使用空间换取时间。在本题中:
- 可以先枚举c和d,将 \(c^2+d^2\) 的值c,d保存下来(顺序存储、哈希)
- 在枚举a,b,计算 \(t = n - a^2-b^2\) ,使用二分法或者哈希来判断t是否已经存储。
- 如果t存在,那么直接输出。
方法1-哈希
使用\(c^2+d^2\)的值直接作为哈希函数,需要注意的是可能有多种方法可以构成某个值,但是只保留第一个,因为第一个字典序最小。
#include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <algorithm> using namespace std; const int N = 5000010; int num[N][2]; int n; int main(void) { scanf("%d",&n); int m = 0; for(int c = 0; c * c <= n;c++ ) for(int d = c;c * c + d * d <= n;d++) { int tmp = c*c + d*d; if(num[tmp][0] == 0 && num[tmp][1] == 0) //在哈希数组还未赋值的情况下,才允许赋值 { num[tmp][0] = c; num[tmp][1] = d; } } for(int a = 0;a * a <= n;a++) for(int b = a; a * a + b * b <= n;b++) { int t = n - a * a - b * b; if(t == 0 || (num[t][0] != 0 || num[t][1] != 0)) //判断t是否存在,存在则直接输出a b c d { printf("%d %d %d %d",a,b,num[t][0],num[t][1]); return 0; } } return 0; }
方法2-二分
二分的话,在于第一步保存\(c^2+d^2\)的值时,如果有重复的,如何做到只保留字典序最小的,这个我能想到的只有遍历,看大佬的程序使用了我不知道的C++语法,贴出来,以供学习。
//作者:yxc #include <cstring> #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int N = 2500010; struct Sum { int s, c, d; bool operator< (const Sum &t)const { if (s != t.s) return s < t.s; if (c != t.c) return c < t.c; return d < t.d; } }sum[N]; int n, m; int main() { cin >> n; for (int c = 0; c * c <= n; c ++ ) for (int d = c; c * c + d * d <= n; d ++ ) sum[m ++ ] = {c * c + d * d, c, d}; //不清楚这里是怎么处理有多种方法可以构成一个值的 sort(sum, sum + m); for (int a = 0; a * a <= n; a ++ ) for (int b = 0; a * a + b * b <= n; b ++ ) { int t = n - a * a - b * b; int l = 0, r = m - 1; //二分 while (l < r) { int mid = l + r >> 1; if (sum[mid].s >= t) r = mid; else l = mid + 1; } if (sum[l].s == t) { printf("%d %d %d %d\n", a, b, sum[l].c, sum[l].d); return 0; } } return 0; }
来源:https://www.cnblogs.com/zy200128/p/12594311.html