这篇文章的题目是在蓝桥杯那个国赛模拟清单里面的,挑了一些会的题.
1. 抓娃娃
第一行输入n
和m
。
接下来的n
行输入所有的线段。
接下来的m
行每一行输入一个区间,然后输出该区间可以包含多少线段,线段不必全部包含,包含一半就好了。
所以我们可以得出一个结论,就是说,如果该区间包含了此线段的中点那么肯定包含了此线段,并不会出现下面的这种情况。
题目中说线段的长度小于等于区间的长度,所以不会出现上图的情况。
- 因此我们只需要记录两条线段的中点出来,然后对其进行排序。
- 然后分别对区间的左右端点进行二分,分别找出左边第一个大于等于左端点的数,和右边最后一个小于等于右端点的数即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, m;
double a[N];
bool cmp(double x, double y)
{
return x < y;
}
int main()
{
scanf("%d%d", &n, &m);
//首先将所给线段转化为中点。
for (int i = 0; i < n; i++)
{
int l, r;
scanf("%d%d", &l, &r);
double mid = (l + r) / 2.0;
a[i] = mid;
}
//将所有的中点进行排序
sort(a, a + n, cmp);
for (int i = 0; i < m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
//第一个二分出左边第一个大于等于l的位置
int l1 = 0, r1 = n - 1;
while (l1 < r1)
{
int mid = l1 + r1 >> 1;
if (a[mid] >= l)
r1 = mid;
else
l1 = mid + 1;
}
//第二次二分求出右边第一个小于等于r的位置
int l2 = 0, r2 = n - 1;
while (l2 < r2)
{
int mid = l2 + r2 + 1 >> 1;
if (a[mid] <= r)
l2 = mid;
else
r2 = mid - 1;
}
//二分之后有结果才去打印。
if (a[l1] >= l && a[l2] <= r)
printf("%d\n", l2 - l1 + 1);
else
printf("0\n");
}
return 0;
}
2. 弹珠堆放
找规律:
i
层的总数量等于i - 1
的数量+i -1
最后一层的数量 + i
;
f[i] = f[i - 1] + ed[i - 1] + i
#include <iostream>
using namespace std;
const int N = 1e5;
int f[N];
int ed[N];
int main()
{
f[2] = 4;
ed[2] = 3;
int i;
for (i = 3; ; i++)
{
f[i] = f[i - 1] + ed[i - 1] + i;
ed[i] = ed[i - 1] + i;
if (f[i] > 20230610)
break;
}
printf("%d\n", i - 1);
return 0;
}
3. 残缺的数字
- 我们将所有的合法二进制数字先记录出来,
- 然后遍历18个所给的字符串。
- 拿每一个字符串和所给的10个字符串进行比较。
- 因为他只能将熄灭的变成点亮的,不能将点亮的变成熄灭的,
- 所以我们只需要判断出如果当前是点亮的,但是合法的是熄灭,他就肯定不能转化成合法的。
#include <iostream>
using namespace std;
char s[20][10];
char h[10][10] = {"1111110", "0110000", "1101101", "1111001", "0110011", "1011011", "1011111", "1110000", "1111111", "1111011"};
int helper(char* s)
{
int res = 0;
for (int i = 0; i < 10; i++)
{
bool is_valid = true;
for (int j = 0; j < 7; j++)
{
//如果当前的不能变,而合法的还是0,那么就一定不能构成0~9中的那一位。
if (s[j] == '1' && h[i][j] == '0')
{
is_valid = false;
break;
}
}
if (is_valid)
res++;
}
return res;
}
int main()
{
// for (int i = 0; i < 18; i++)
// scanf("%s", &s[i]);
// int res = 1;
// for (int i = 0; i < 18; i++)
// res *= helper(s[i]);
//printf("%d\n", res);
printf("254016000");
return 0;
}
4. 不完整的表达式
题目给定我们一个表达式,然后其中抹掉了一个,可以是数字,也可以是操作符,我们需要求出其抹掉的是什么,并且题目保证了三个数都是正整数。
- 我们直接模拟就好,如果操作符存在,那么就对其剩余的两个数进行逆运算就好了
- 如果操作符不存在,将其三个数进行模拟推出操作符即可。
我的代码又臭又长。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 15;
char s[N];
int a[3];
int pos = 0;
char op = 0;
int eval_c()
{
int x = a[0], y = a[1];
if (op == '+')
return x + y;
else if (op == '-')
return x - y;
else if (op == '*')
return x * y;
else
return x / y;
}
int eval_b()
{
int x = a[0], z = a[2];
if (op == '+')
return z - x;
else if (op == '-')
return x - z;
else if (op == '*')
return z / x;
else
return x / z;
}
int eval_a()
{
int y = a[1], z = a[2];
if (op == '+')
return z - y;
else if (op == '-')
return y + z;
else if (op == '*')
return z / y;
else
return y * z;
}
int main()
{
a[0] = a[1] = a[2] = -1;
scanf("%s", &s);
int len = strlen(s);
for (int i = 0; i < len; i++)
{
//求出两个数
if (isdigit(s[i]))
{
int num = 0, j = i;
while (j < len && s[j] != '=' && isdigit(s[j]))
num = num * 10 + s[j++] - '0';
i = j - 1;
//遍历到最后一个数,第二个数还是-1,就说明第二个数让擦掉了。
if (j == len && a[1] == -1)
pos++;
a[pos++] = num;
}
else if (s[i] != '=' && s[i] != '?')
{
//遍历到了op第一个数还是-1,就说明第一个数被擦了。
if (a[0] == -1)
pos++;
op = s[i];
}
}
//op未被擦掉
if (op != 0)
{
//被擦掉的是 C
if (a[0] != -1 && a[1] != -1)
printf("%d\n", eval_c());
else if (a[0] != -1)
printf("%d\n", eval_b());
else
printf("%d\n", eval_a());
}
else //求解op
{
//表达式肯定合法,三个数肯定全部有值才行。
int x = a[0], y = a[1], z = a[2];
if (x + y == z)
printf("+\n");
else if (x - y == z)
printf("-\n");
else if (x * y == z)
printf("*\n");
else
printf("/\n");
}
return 0;
}
5. 游戏
这道题一眼就是滑动窗口,并且还是最简单的滑动窗口模板题。
- 根据滑动窗口求出最大值和最小值后。
- 如果对其每一个最大值和最小值进行相减,那么时间复杂度是 O ( n 2 ) O(n^2) O(n2)的必然会超时的。
- 所以我们可以对其进行一个公式的优化。
- 期望 = (最大值总和 - 最小值总和 )/ (n - k + 1)
n - k + 1
也就是是最大值或者最小值的总个数。
注意:记得开long long,它真的, 我哭死。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, k;
int a[N];
int que[N], front, rear;
int bear1[N], bear2[N]; //熊大,熊二
int main()
{
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
int pos = 0;
for (int i = 0; i < n; i++)
{
if (front != rear && que[front] <= i - k) front++;
while (front != rear && a[que[rear - 1]] <= a[i]) rear--;
que[rear++] = i;
if (i >= k - 1)
bear1[pos++] = a[que[front]];
}
pos = 0;
front = rear = 0;
for (int i = 0; i < n; i++)
{
if (front != rear && que[front] <= i - k) front++;
while (front != rear && a[que[rear - 1]] >= a[i]) rear--;
que[rear++] = i;
if (i >= k - 1)
bear2[pos++] = a[que[front]];
}
LL sum = 0;
for (int i = 0; i < pos; i++)
sum += bear1[i];
for (int i = 0; i < pos; i++)
sum -= bear2[i];
printf("%.2f", (double)sum / pos);
return 0;
}
6. 互质
两数的最大公约数是 1 1 1则两数互为质数
#include <iostream>
using namespace std;
int n = 1018;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int res = 1; //1 与任何数互为质数
for (int i = 2; i <= 2020; i++)
if (gcd(i, n) == 1) //最大公约数是1则两数互为质数
res++;
printf("%d\n", res);
return 0;
}
7.火车运输
这道题目与01背包问题十分相似,01背包问题是只有一个背包,然后我们这边是有两个背包,我们在01背包问题时候有两种决策,选与不选。
而在这个问题里面则有三种,不选,选择放入A车,选择放入B车。
- 状态表示:
f[i][j][k]
表示从前i
个物品中选,体积不超过j
并且体不超过k
的所有方案。- 属性:
- 将所有的方案的价值取
max
- 状态计算:
- 与01背包一模一样,只不过多了一个参数而已。
- 01不选:
f[i][j] = f[i - 1][j]
- 该题不选:
f[i][j][k] = f[i - 1][j][k]
- 01背包选择该物品:
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
- 该题选择放入A:
f[i][j][k] = max(f[i][j][k], f[i - 1][j - v[i]][k] + v[i])
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n, A, B;
int v[N];
int f[N][N][N]; //f[i][j][k] 表示从前i个物品中选。 A车总体积不超过j B车总体积不超过k的最大运输.
int main()
{
scanf("%d%d%d", &n, &A, &B);
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for (int i = 1; i <= n; i++)
for (int j = 0; j <= A; j++)
for (int k = 0; k <= B; k++)
{
//不选
f[i][j][k] = f[i - 1][j][k];
//选择放入A
if (j >= v[i])
f[i][j][k] = max(f[i][j][k], f[i - 1][j - v[i]][k] + v[i]);
//选择放入B
if (k >= v[i])
f[i][j][k] = max(f[i][j][k], f[i - 1][j][k - v[i]] + v[i]);
}
printf("%d\n", f[n][A][B]);
return 0;
}
题目中的数据范围A,B最大是1000,我们只能开到310,不然就是爆内存,所以这道题利用这个方法并不能全过,只能过70%.
由此我们可以想到利用优化01背包的方式。
利用滚动数组进行优化这个问题,将三维优化成二维就好了。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, A, B;
int v[N];
int f[N][N]; //f[i][j][k] 表示从前i个物品中选。 A车总体积不超过j B车总体积不超过k的最大运输.
int main()
{
scanf("%d%d%d", &n, &A, &B);
for (int i = 1; i <= n; i++)
scanf("%d", &v[i]);
for (int i = 1; i <= n; i++)
for (int j = A; j >= 0; j--)
for (int k = B; k >= 0; k--)
{
//选择放入A
if (j >= v[i])
f[j][k] = max(f[j][k], f[j - v[i]][k] + v[i]);
//选择放入B
if (k >= v[i])
f[j][k] = max(f[j][k], f[j][k - v[i]] + v[i]);
}
printf("%d\n", f[A][B]);
return 0;
}
8. 最大区间
题目要求我们求出一个区间[l,r]``,在此区间内的最小值 乘 区间长度 的值最大。
题目中的测试用例是选择[2,3]
这个区间最小值是
3
3
3长度是
2
2
2所以答案就是
2
∗
3
=
6
2*3=6
2∗3=6
(1)单调队列
- 这道题目我们首先暴力来想就是枚举所有的区间 l e n = [ 1 , n ] len = [1, n] len=[1,n]
- 对每个区间进行滑动窗口求出其最小值,然后枚举所有的结果。
- 但是一定是会超时的,能过45%。
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
int n;
int a[N];
int que[N], front, rear; //队列中存放着每个区间最小值
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
LL res = 0;
for (int len = 2; len <= n; len++)
{
int maxv = -1e9;
front = rear = 0;
for (int i = 0; i < n; i++)
{
if (front != rear && que[front] <= i - len) front++;
while (front != rear && a[que[rear - 1]] >= a[i]) rear--;
que[rear++] = i;
if (i >= len - 1)
maxv = max(maxv, a[que[front]]); //对每个区间的最小值取最大值
res = max(res, (LL)len * maxv);
}
}
printf("%lld\n", res);
return 0;
}
(2)单调栈
这道题的正解其实是用单调栈来做的。
- 我们对序列中的每一个元素,分别求出其左边第一个比其小的位置,右边第一个比其小的位置,分别用
l[i]和r[i]
来记录 - 那么
l[i]
到r[i]
之间就是自身最小的最大长度。 - 然后枚举其所有答案取最大值就好了。
(r[i] - l[i] - 1) * a[i].
如果序列全是相同的数字,那么其应该输出长度为1的那个数字。
所以给res赋值应该赋值成序列中的第一个数.
顺带提一嘴,它没有这个测试用例!!!.
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10;
typedef long long LL;
int n;
int a[N], l[N], r[N]; //l, r 分别记录左右第一个比a[i]小的下标
int stk[N], top;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
//左边
for (int i = 1; i <= n; i++)
{
while (top != 0 && a[stk[top]] >= a[i]) top--;
l[i] = stk[top];
stk[++top] = i;
}
//右边
top = 0;
for (int i = n; i >= 1; i--)
{
while (top != 0 && a[stk[top]] >= a[i]) top--;
r[i] = stk[top];
stk[++top] = i;
}
LL res = a[1];
for (int i = 1; i <= n; i++)
res = max(res, (LL)(r[i] - l[i] - 1) * a[i]);
printf("%lld\n", res);
return 0;
}
9. 翻转
这道题目要求我们将所有给定的字符串拼接在一起,如果前面的后缀和后面的前缀相同,则可以抵消一个字母,问所有的字符串全部合并的最短长度是多少。
- 首先对于每一个字符串只有两种操作 — 翻转或者不翻转
- 那么两两操作的话就会有4种排列组合。
- 分别是:
- 当前正常,前一个正常。
- 当前正常,前一个翻转。
- 当前翻转,前一个正常。
- 当前翻转,前一个翻转。
- 我们针对于这四个情况,列出4种相应的方程即可。
f[i][0] 表示到第i个字符串的时候正常排列的最短长度
f[i][1]表示到第i个字符串的时候非正常(翻转)排列的最短长度
代码中的诸如s[i - 1][1] != s[i][0]
此类语句的意思就是判断翻转或者不反转的前一个后缀和后一个前缀是否相同,如果不同,我们就+1,如果相同就+0.
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n;
char s[N][3];
//f[i][0] 表示到第i个字符串的时候正常排列的最短长度
//f[i][1]表示到第i个字符串的时候非正常(翻转)排列的最短长度
int f[N][2];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%s", s[i]);
memset(f, 0x3f, sizeof f);
//初始化
f[1][0] = f[1][1] = 2; //第一个字符串无论如何长度都是2
//dp
for (int i = 1; i <= n; i++)
{
// 当前正常 前一个正常
f[i][0] = min(f[i][0], f[i - 1][0] + 1 + (s[i - 1][1] != s[i][0]));
// 当前正常 前一个翻转
f[i][0] = min(f[i][0], f[i - 1][1] + 1 + (s[i - 1][0] != s[i][0]));
// 当前翻转 前一个正常
f[i][1] = min(f[i][1], f[i - 1][0] + 1 + (s[i - 1][1] != s[i][1]));
// 当前翻转 前一个翻转
f[i][1] = min(f[i][1], f[i - 1][1] + 1 + (s[i - 1][0] != s[i][1]));
}
printf("%d\n", min(f[n][0], f[n][1]));
return 0;
}