今天完成了蓝桥杯中二分和前缀和问题的训练
第一题
机器人向前跳增加二倍能量减少下一层能量
二分算法能够解决包括但不限于单调递增的问题
故我们寻找最小需要的能量值
创建check方法 如果通过返回true 继续进行二分查找 不通过的话则将左区间定为mid+1
最后输出 右端点即为结果
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int h[N];
bool check(int e){
for(int i = 0; i < n; i ++ ){
e = e*2 - h[i];
if(e < 0) return false;
if(e > 1e5) return true;
}
return true;
}
int main(){
scanf("%d",&n);
for(int i = 0; i < n; i++) scanf("%d",&h[i]);
int l = 0, r = 1e5;
while(l < r){
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n",r);
return 0;
}
第二题
一个数化为四个数的平方
这个题由于对于时间有所限制 暴力通过四重循环无法解决
选择用空间换时间进行解决
先进行二重循环 将每一次循环的值和 两数平方的和存储
再进行二重循环 将剩下两个数的平方和存储
学习到了在循环体内定义新的operation<方法
由于循环体要进行sort排序 我们定义循环的优先级
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;
}
//这里优先级 s > c > d
代码如下:
/*
一个数字求四个数字的平方和
*/
#include <bits/stdc++.h>
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;
}
第三个题目
这个题目比较简单 将n块巧克力分给n个小朋友 其中给出巧克力尺寸和块数目
求出最大每个小朋友能得到边长为多少的正方形巧克力
单调区间内问题 使用二分的方法进行遍历
对每一个边长进行遍历 创建check方法 如果可以就返回true 不可以就返回false
/*
分巧克力问题 ,能用数组解决尽量用数组解决。。
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, k;
int h[N], w[N];
bool check(int mid)
{
int res = 0;
for (int i = 0; i < n; i ++ )
{
res += (h[i] / mid) * (w[i] / mid);
if (res >= k) return true;
}
return false;
}
int main()
{
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d%d", &h[i], &w[i]);
int l = 1, r = 1e5;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d\n", r);
return 0;
}
发现自己对结构体的使用不熟练。
第四题
题目要求尽可能多的炸到边长要求内的最高分数
我们选择使用前缀和遍历的方法
/*
激光炸弹问题
前缀和问题
处理前缀和时将边界都+1 避免边界问题
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
int m, n;
int s[N][N];
int main(){
int cnt, R;
cin >> cnt >> R;
R = min(5001,R);
n = m = R;
while(cnt -- ){
int x, y, w;
cin >> x >> y >> w;
x ++, y ++;
n = max(x, n);
m = max(y, m);
s[x][y] += w;
}
for(int i = 1; i <= n; 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 ret = 0;
for(int i = R; i <= n ;i ++ ){
for(int j = R; j<= m; j ++){
ret = max(ret, s[i][j] - s[i-R][j] - s[i][j-R] + s[i-R][j-R]);
}
}
cout << ret << endl;
return 0;
}
先计算出前缀和数组 然后对前缀和数组进行边长为R的 矩阵计算 计算中有很多小细节需要注意。
第五题
要求求一个数组 有多少个区间是 某个数的k倍数
这个题的思想层层递进
首先我们想到了暴力的方法
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N],s[N];
int main(){
int n,k;
cin >> n >> k;
for(int i = 1; i <= n; i ++ ) scanf("%d",&a[i]);
s[1] = a[1];
s[0] = a[0] = 0;
for(int i = 2; i <= n; i ++ ) s[i] = a[i] + s[i-1];
//准备好了前缀和数组
int ret = 0, target = 0;
for(int i = 1; i <= n; i ++ ){
for(int j = i;j <= n; j ++ ){
target = s[j] - s[j-i];
if(target > 1 && (target%k) == 0) ret ++ ;
}
}
cout << ret << endl;
return 0;
}
这个方法中我们先计算出了前缀和数组 然后对前缀和数组进行了双重遍历
遍历每一种区间长度的 全部区间 如果成立 就记录加一
进阶算法:由于前缀和数组的特性 如果数组的前缀和R 和 L 对于某个值K的模是相同的 ,那么这个区间 是值K的倍区间 因为他们有相同的余数 相减后 刚好得到倍K
由此思想 我们可以先建立一个数组cnt 来存储每一个前缀和关于数K的余数
在计算的过程中遍历
cnt[0] = 1;
for(int i = 1; i <= n; i ++ ){
ret += cnt[s[i] % k];
cnt[s[i] % k] ++ ;
}
/*
首先cnt[0] = 1 是因为如果后面的数有余数为0的情况时 和前面所有的数之和就是K倍区间
然后进行一轮一轮的运算 每走一步统计前面的cnt数组情况
再经过回顾 将ret进行计数
秒哇!