第一题:自动收麦机
题目描述
在游戏MC(我的世界)中,如果小麦碰到水流就会掉落,但是mc中的水不能无限流动,在同一高度一桶水往一个方向流最多可以流动8格(算上本身一格)。
但是如果流动过程中遇到了阶梯下降了高度,则从下降的那一格开始重新计算距离,所以根据这个原理可以设计一种自动收小麦机,只需要从最高处倒一桶水就可以把所有小麦变成掉落物。
这次小小航也根据这个原理建造了一个自动收小麦机,但是小小航并没有精确的计算台阶的位置,当小小航建造完后发现机器不能一次收集所有小麦,现在已知小小航的收小麦机总长为 n 格 ,水流可以流 k 格,每一格上的小麦数量为ai。
共有q次询问(每次询问之间互不影响),每次询问一个整数x,在第x格放一桶水共可以收获多少小麦。(水只会从高的一端流向低的一端)
输入描述:
第一行三个整数n,q,k(1≤n,q,k≤105)(1\leq n,q,k \leq 10 ^ 5)(1≤n,q,k≤105)
第二行 n个数表示每格上小麦的数量(0≤数量≤109)(0\leq数量 \leq 10^9)(0≤数量≤109)
第三行n个数表示每格的高度,保证非递减(0≤高度≤109)(0\leq 高度 \leq 10^9)(0≤高度≤109)
接下来q行 每行一个数x,表示在第x格放一桶水(1≤x≤n)(1\leq x \leq n)(1≤x≤n)
输出描述:
q行,每行一个整数,表示能收获的小麦数量。
示例1
输入
4 1 2 1 1 4 5 2 2 2 3 4
4 1 2 1 1 4 5 2 2 2 3 4
输出
10
说明
在第4格放出水流后,水流会流向第3格,由于第3格高度比第4格低,所以水流继续向左流向第2格,因为平地水流只能流2格,所以到达第2格后水流停止,收获的小麦数量为1 + 4 + 5 = 10
输入
5 2 2 1 1 4 5 1 2 2 3 3 4 4 3
5 2 2 1 1 4 5 1 2 2 3 3 4 4 3
输出
9
6
解题思路:
对于每次询问中的一个整数 x,需要计算在第 x 格放置水桶后可以收获多少小麦。
根据题目描述,如果水流遇到了台阶下降了高度,则从下降的那一格开始重新计算距离,因此我们可以根据 st 数组找到第 x 格往下最远能流到哪一格。然后根据前缀和数组 sum,计算第 x 格和最远能流到的那一格之间的小麦数量。
具体代码实现:首先读入 n、q 和 k,然后通过一个循环读入每个位置的高度,并计算出麦田小麦的前缀和。接下来,初始化麦田高度数组 h[0] 为 -1,前一个台阶距离数组 tj[1] 为 1,并使用变量 idx 来记录当前位置距离最左侧的距离。
在另一个循环中,依次读入每个位置的高度,并根据相邻高度的差异来更新前一个台阶的距离数组 tj 和最远流到的位置数组 st。如果当前位置的前一个台阶距离小于等于 k,则表示可以流入下一个台阶,将下一个台阶的距离设置为上一个台阶的距离。否则,将下一个台阶的距离设置为当前位置与最左侧的距离减去 k。
最后,在一个循环中,依次读入每个询问的位置 x,并输出该位置的小麦前缀和减去最左侧位置的小麦前缀和。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, M = 1e5 + 5, INF = 0x3f3f3f3f;
int n, q, k;
ll h[N], sum[N]; // 麦田的高度和麦田小麦的前缀和
int tj[N], st[N]; // 前一个台阶的距离和最远流到的位置
int main() {
cin >> n >> q >> k;
for (int i = 1; i <= n; i++) {
int x;
cin >> x; // 求前缀和
sum[i] = sum[i - 1] + x;
}
h[0] = -1; // 麦田的高度
tj[1] = 1; // 前一个台阶的距离
int idx = 0; // 记录距前一个台阶的距离
for (int i = 1; i <= n; i++) {
cin >> h[i];
if (h[i] != h[i - 1]) tj[i] = idx = 1;
else tj[i] = idx;
idx++;
if (tj[i] < k) st[i] = st[i - 1]; // 能够流入下一个台阶
else st[i] = i - k + 1; // 无法流入下一个台阶
}
while (q--) {
int x;
cin >> x;
cout << sum[x] - sum[st[x] - 1] << endl;
}
return 0;
}
固执的RT
题目描述
RT你的帮助下取走了足够多的树枝,并用木制的马车拉着树枝去与人马决一死战。但他忘了一件事,人马是会喷火的。在战斗进行到一半时,卑鄙的人马用火焰将RT所有树枝烧光了。一般人可能已经放弃了,但固执的RT不愿意放弃。他决定再去收集树枝,和人马进行第二次战斗。
RT现在收集了n个树枝,第i个树枝的攻击力为ai。RT如果想打败人马,收集到的树枝攻击力之和至少为m。请你帮RT判断他现在收集到的树枝是否足够打败人马。
如果可以请输出“YES”, 否则输出“NO”(输出不带引号)。
输入描述:
第一行两个整数n,m。分别是树枝的数量和RT需要收集的树枝攻击力之和的最低要求。(1≤\leq≤n≤\leq≤1e5, 1≤\leq≤m≤\leq≤1e9)
第二行n个正整数,代表n个树枝的攻击力。(1≤\leq≤ai≤\leq≤1e9)
输出描述:
一行,如果RT收集到的树枝可以击败人马,输出“YES”,否则输出“NO”
示例1
输入
5 9
3 6 8 7 4
输出
YES
示例2
输入
5 9
1 1 1 1 1
输出
NO
解题思路:
前缀和统计前n个树枝攻击力的总和,然后与m作比较即可。
#include<iostream>
#include<cstring>
using namespace std;
long long n,m,x;
long long sum[100005];
int main(){
cin >> n >> m;
for(int i=1; i<=n; i++){
cin >> x;
sum[i] = x + sum[i-1];
}
if(sum[n] >=m ) cout << "YES";
else cout << "NO";
}
释怀的RT
题目描述
又一次收集完树枝后,RT做了防火工作,这次RT成功的使用了树枝战胜了人马。经过了长时间的痛苦折磨,RT释怀了并决定去海拉卢达陆上狩猎岩石巨人,用岩石巨人的心岩照亮心形湖来祭奠他还未开始就结束的爱情。
假设心形湖由1××n个方格构成,RT在每个方格上放了一个心岩,每个心岩有一个照亮范围x,代表着这块心岩可以照亮它左边x个方格和右边x个方格,但不能照亮心岩所在的方格(假如一个心岩在第5个方格,x=2,那么他只能照亮第3,4和第6,7个方格),现在请你求出心形湖有多少个方格被照亮。
输入描述:
第一行一个正整数n,心形湖格子的个数。( 1 ≤≤ n ≤≤ 1e6)
第二行n个整数,第i个数表示第i个心岩能照亮的范围。(0 ≤≤��xi ≤≤ 1e9)
输出描述:
一行,一个整数,表示照亮的格子数
示例1
输入
5 0 1 0 0 10
输出
4
说明
前四个方格被最后一个心岩照亮
示例2
输入
5 0 1 0 0 1
输出
3
说明
第一个方格和第三个方格被第二个格子的心岩照亮,第四个方格被第五个格子的心岩照亮,一共有三个格子被照亮
解题思路:
可以将第i个心岩的照亮当成对[i - x, i - 1], [i + 1, i + x]这两个区间的每一个格子加1,最后判断格子是否大于0,如 果大于0,说明至少被1个心岩照亮,否则就没有被照亮。区间用的是差分来进行处理。
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, x, pre[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) { / / 差分
cin >> x;
pre[max(1, i - x)]++, pre[i]--;
pre[i + 1]++, pre[min(n + 1, i + x + 1)]--;
}
int cnt = 0, now = 0;
for (int i = 1; i <= n; i++) {
now += pre[i];
cnt += now > 0;
}
cout << cnt << endl;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
奶牛的寿命
题目描述
上帝养了一头奶牛,但是有一天奶牛偷吃了上帝草药,上帝十分生气于是奶牛必须要去地狱接受惩罚,上帝给了奶牛一个长达n的刑期但是同样允许奶牛对刑期进行最多操作log2(n)+1次,每次操作可以交换刑期的二进制形式下任意的两个位置上的数字,但是不能改变原来的二进制数的位数(不能有前导零),奶牛自然希望可以尽可能多的减刑,请问出奶牛最多可以减少多少刑期?
输入描述:
一个正整数n表示奶牛当前刑期。(n<2^31)
输出描述:
一个整数表示奶牛最多可以减少的刑期。
示例1
输入
14
输出
3
说明
只需要交换第1位和第3位,14(1110)-->11(1011),14-11=3。
解题思路:
标记出输入的十进制转化为二进制后,1出现的次数。然后开始创造重新组合的最大值,因为不含前导零,所以第一项只能是1,剩下的0尽可能的往前面放,1尽可能的往后放。
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
bitset<31> m(n);
int num = m.count() - 1;//num为二进制一的个数‐1 (因为遍历到0)
bool flag = 0;//判断有没有出现第一个1
for (int i = 30; i >= 0; i--) {
if (i < num)m[i] = 1; // 当没有0的时候,即必须补1
else if (flag)m[i] = 0;
if (m[i] & 1) flag = 1;
}
cout << n - m.to_ulong();// to_ulong() 是将二进制的数转换成是十进制的(无符号)长整型数
return 0;
}