蓝桥杯 --- 二分与前缀和(习题)

蓝桥杯 --- 二分与前缀和(习题)

  • 730. 机器人跳跃问题
  • 1221. 四平方和
  • 1227. 分巧克力
  • 99. 激光炸弹
  • 1230. K倍区间

730. 机器人跳跃问题

机器人正在玩一个古老的基于 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)≤105

输入样例1:

5
3 4 3 2 4

输出样例1:

4

输入样例2:

3
4 4 4

输出样例2:

4

输入样例3:

3
1 6 4

输出样例3:

3

解题思路
二分能量值,找到一个最小的符合要求的最小值即可

#include

using namespace std;

const int N = 1e5 + 10;

int n;
int f[N];

bool check(int mid) {
	
	for(int i = 0; i < n; i ++ ) {
		mid = mid * 2 - f[i];
		if(mid > 1e5) return true;
		if(mid < 0) return false;	
	}
	return true;
	
}

int main() {
	
	cin >> n;
	for(int i = 0; i < n; i ++ ) cin >> f[i];
	
	int l = 0, r = N;
	while(l < r) {
		int mid = l + r >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	
	cout << l << endl;
	
	return 0;
	
}

1221. 四平方和

四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多 4 个正整数的平方和。

如果把 0 包括进去,就正好可以表示为 4 个数的平方和。

比如:

在这里插入图片描述

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 4 个数排序:

0≤a≤b≤c≤d

并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。

输入格式
输入一个正整数 N。

输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。

数据范围

06

输入样例:

5

输出样例:

0 0 1 2

解题思路
直接暴力,四层循环,数据范围是 5∗106 会直接超时
优化方式,先枚举 c 和 d,将结果存储下来,再枚举 a 和 b,查看是否能够找到符合条件的 c 和 d,时间复杂度直接减半
用空间替换时间!

#include
#include

using namespace std;

const int N = 2500010;

struct Sum{
	int sum, c, d;
	bool operator < (const Sum &t) const {
		if(sum != t.sum) return sum < t.sum;
		if(c != t.c) return c < t.c;
		return d < t.d;
	} 
}sum[N];

int main() {
	
	int n, m;
	cin >> n;
	//先枚举c和d 
	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); 
	
	//再枚举a和b 
	for(int a = 0; a * a <= n; a ++ ) {
		for(int b = 0; b * b + a * a <= 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].sum >= t) r = mid;
				else l = mid + 1;
			}
			if(sum[l].sum == t) {
				cout << a << ' ' << b << ' ' << sum[l].c << ' ' << sum[l].d << endl;
				return 0;
			} 
		}
	}
	
	return 0;
	
} 

1227. 分巧克力

儿童节那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。

为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。

切出的巧克力需要满足:

形状是正方形,边长是整数大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行包含两个整数 Hi 和 Wi。

输入保证每位小朋友至少能获得一块 1×1 的巧克力。

输出格式
输出切出的正方形巧克力最大可能的边长。

数据范围

1≤N,K≤105, 1≤Hi,Wi≤105

输入样例:

2 10
6 5
5 6

输出样例:

2

解题思路
二分满足条件的最大边长!!!
二分的时候又出错了…
二分条件可以查看一下这篇博客:https://www.acwing.com/blog/content/307/
写的很好!

#include

using namespace std;

const int N = 1e5 + 10;

int n, k;
int h[N], w[N];

bool check(int mid) {
	
	int sum = 0;
	for(int i = 0; i < n; i ++ ) {
		sum += (h[i] / mid) * (w[i] / mid);
	}
	if(sum >= k) return true;
	return false;
	
}

int main() {
	
	cin >> n >> k;
	int res = 0x3f3f3f3f;
	for(int i = 0; i < n; i ++ ) {
		cin >> h[i] >> w[i];
// 		res = min(h[i], res);
// 		res = min(w[i], res);
	}
	
	//二分查找边长
	//这里不能使用最小值,因为有的比较小的巧克力是可以不选择的!!!
// 	int l = 0, r = res;
    int l = 0, r = N;
	while(l < r) {
		int mid = l + r + 1 >> 1;
		if(check(mid)) l = mid;
		else r = mid - 1;
	} 
	
	cout << l << endl;
	
	return 0;
}

99. 激光炸弹

地图上有 N 个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式
第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤109
0 0≤Xi,Yi≤5000
0≤Wi≤1000

输入样例:

2 1
0 0 1
1 1 1

输出样例:

1

感觉范围有点问题…

#include

using namespace std;

const int N = 5010;

int n, r;
int s[N][N];

int main() {
	
	cin >> n >> r;
	r = min(5001, r);
	int l = 0;
	int m = 0;
	l = m = r;
	for(int i = 0; i < n; i ++ ) {
		int x, y, w;
		cin >> x >> y >> w;
		s[x + 1][y + 1] += w;
		l = max(l, x + 1);
		m = max(m, y + 1);
	}
	
	for(int i = 1; i <= l; i ++ ) {
		for(int j = 1; j <= m; j ++ ) {
			s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
		}
	}
	
	int ans = 0;
	
	for(int i = r; i <= l; i ++ ) {
		for(int j = r; j <= m; j ++ ) {
			ans = max(ans, s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r]);
		}
	}
	
	cout << ans << endl;
	
	return 0;
	
}

1230. K倍区间

给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式
输出一个整数,代表 K 倍区间的数目。

数据范围

1≤N,K≤100000, 1≤Ai≤100000

输入样例:

5 2
1
2
3
4
5

输出样例:

6
#include 

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n, k;
//这里要用ll,Ai相加后可能会爆int 
ll s[N];
ll p[N];

int main() {
	
	cin >> n >> k;
	for(int i = 1; i <= n; i ++ ) {
		cin >> s[i];
		s[i] += s[i - 1];
	}
	ll ans = 0;
	p[0] = 1;
	for(int i = 1; i <= n; i ++ ) {
		//这里先加后更新
		//原因:
		//对于p[0]来说存在一个就符合条件,对于p[1]来说要存在两个才能符合条件
		//因此初始化p[0] = 1,然后先加够更新 
		ans += p[s[i] % k];
		p[s[i] % k] ++ ;
	}
	cout << ans << endl;
	
}

你可能感兴趣的:(蓝桥杯,蓝桥杯,算法,职场和发展)