挖土机编程 CSP-J 2022 模拟赛 Round 2
闲来无事, 做一套CSP-J普及组的题找找状态(
基本都是贪心和模拟题, 适合热手。
1 探索未知
P8584 探索未知 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
模拟一下分数相加相减就行, 注意负数处理。
一个\(\frac{a}{b}\)分数化为最简分数时, 只需要把分子分母同除以分子与分母的最大公约数即可。
即:\(\frac{a/gcd(a,b)}{b/gcd(a,b)}\)
两个分数之间通分则先求出分母之间的最小公倍数, 然后这个公倍数就是新的分母, 前一个的分子乘上公倍数除以前一个的分母, 后一个的分子乘上公倍数除以后一个的分母, 然后相加减。
即:\(\frac{a}{b} + \frac{c}{d}=\frac{a*\frac{lcm(b,d)}{b} + c* \frac{lcm(b,d)}{d}}{lcm(b,d)}\)
用flag记录当前的数的符号。
代码
#include <iostream>
using namespace std;
long long gcd(long long a, long long b)
{
if (a < b)
gcd(b, a);
return b ? gcd(b, a % b) : a;
}
int main()
{
long long n, res_a = 0, res_b = 1, flag = 1; // 1代表正数, 0代表负数
cin >> n;
while (n--)
{
long long a, b, op;
cin >> a >> b >> op;
if (op == 1)
{
long long t = res_b * b / gcd(res_b, b);
if (flag)
res_a = res_a * t / res_b + a * t / b;
else
res_a = res_a * t / res_b - a * t / b;
if (res_a < 0)
res_a = -res_a, flag = !flag;
res_b = t;
t = gcd(res_a, res_b);
res_a /= t;
res_b /= t;
}
else if (op == 2)
{
long long t = res_b * b / gcd(res_b, b);
if (flag)
res_a = res_a * t / res_b - a * t / b;
else
res_a = res_a * t / res_b + a * t / b;
if (res_a < 0)
res_a = -res_a, flag = !flag;
res_b = t;
t = gcd(res_a, res_b);
res_a /= t;
res_b /= t;
}
}
if (!flag)
cout << "-";
cout << res_a;
if (res_b != 1)
cout << "/" << res_b;
return 0;
}
2 球状精灵的传说
P8585 球状精灵的传说 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
乍一看还挺复杂, 不过有一句说,新形成的精灵不再参与拥抱, 这么说的话, 最终答案只有两个方向:
- 单个精灵的声望
- 任意两个精灵拥抱后的声望
对于单个精灵很好算, 遍历一遍就能求出来最大的那个。
而任意两个精灵则需要些思考:
朴素方法枚举所有两两组合肯定是不过的, 复杂度为 \(O(n^2)\)
朴素方法不行那就只能看看有啥规律没有, 对于这两个精灵, 他们拥抱的规则是:
至少有两个值相同时才能拥抱, 拥抱后的精灵将另外的值相加。
即 {a,x,y} + {b,x,y} = {a + b, x, y}
又根据声望的定义, 他是以最小的r为基准, 也就是说, 当相加的值不是最小的哪个时, 其实合并后的精灵的声望是没变化的, 这个情况在单个精灵就求过。
那什么时候可以变化呢?当 a
是 min(a,x,y)
, b
是 min(b, x, y)
时, 此时合成后的精灵才会比之前两个的声望高, 才是对正解有贡献的有效合成。
这样的话, 我们把输入的 {r1, r2, r3}
重新排序, 让最小的在前面, 后面两个随意。
如何找到当前球最好的合成呢?
需要找到 r2,r3
相同的另一个球, 那么我们把其按 r2,r3
从小到大排序时, 能合成的球肯定相邻。
然后就直接让 ball[i] 与 ball[i - 1]
合成(在他俩r2,r3相同情况下), 然后记录所有合成情况的最大值即可。
但这样有可能其r1
向加不是最大值, 可以让其在 r2,r3
相等时, 以 r1
从小到大排序。
这样无论怎么求都是最大的和次大的相加。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10;
struct Ball {
int r1, r2, r3, id;
bool operator < (const Ball& W) const {
if (r2 == W.r2)
return r3 < W.r3;
return r2 < W.r2;
}
int get_p() {
int minx = min(min(r1, r2), r3);
return minx * minx * minx / 4;
}
}b[N];
int n, m;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int a[3];
cin >> a[0] >> a[1] >> a[2];
sort(a, a + 3);
b[i] = { a[0], a[1], a[2], i};
}
sort(b + 1, b + 1 + n);
int one = 0, one_i = 0;
int sum = 0, sum_i = 0, sum_j = 0;
for (int i = 1; i <= n; i++)
{
if (b[i].r2 == b[i - 1].r2 && b[i].r3 == b[i - 1].r3)
{
Ball t = { b[i].r1 + b[i - 1].r1, b[i].r2, b[i].r3, i };
if (sum < t.get_p())
{
sum = t.get_p();
sum_i = b[i].id, sum_j = b[i - 1].id;
}
}
if (one < b[i].get_p())
{
one = b[i].get_p();
one_i = b[i].id;
}
}
if (one >= sum)
cout << 0 << "\n" << one_i << "\n" << one << "\n";
else
cout << 1 << "\n" << min(sum_i, sum_j) << " " << max(sum_i, sum_j) << "\n" << sum << "\n";
return 0;
}
3 星环防御工事
P8586 星环防御工事 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
根据天数来思考, 当前天数能拦截的只有上一天剩下的, 和今天的。
因为每天最多拦截k质量, 所以最优的解法就是每天都拦截k的质量, 这样得到的总质量是最大的。又因为上一天的只能今天拦截, 今天的还能明天拦截, 所以肯定先拦截昨天的会更好。
定义 prev
表示昨天能拦截的质量, now
为今天能拦截的质量 :
prev = a[i - 1] > k ? k : a[i - 1]
大于k时最多拦截k个
now = (k - prev) > a[i] ? a[i] : (k - prev)
(k-prev)
代表拦截昨天后还剩下能拦截的质量, 如果当前需要拦截的质量大于能拦截的质量, 则只能拦截一部分。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10;
int n, k, m;
int a[N];
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
int day, cost;
cin >> day >> cost;
a[day] += cost;
m = max(day, m);
}
int res = 0;
for (int i = 1; i <= m + 1; i++)
{
int prev = a[i - 1] > k ? k : a[i - 1];
int now = (k - prev) >= a[i] ? a[i] : (k - prev);
res += prev + now;
a[i] -= now;
}
cout << res << endl;
return 0;
}
4 新的家乡
P8587 新的家乡 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给n个数, 求出这些数能组成数 x 的个数, 且求出最大个数时有几种 x。
其组成规则只能是由一个数加上另一个数:
a + b = x
转换一下:
x - a = b
因为x可以取任意的值, 显然需要用循环来枚举x。我们得到x之后便可以枚举a, 然后直接算出b。找到a,b之后就需要判断数组中是否存在这两个数, 若存在则可以用这个x。
对于柱子数量则可以通过求多少个 a 满足 x - a
和 a
同时存在于数组中。枚举x时如果柱子数量等于之前的最大柱子数量则将ans + 1
。
那么这样我们就能把答案求出来了, 这里还有一个问题, 如何快速判断 x-a
和 a
是否存在于数组中:
最暴力的方法就是遍历整个数组, 求出来 x - a的个数
和 a的个数
, 复杂度为 \(O(n)\)
既然是查找, 可否用二分来求?当然不能, 二分不能求出来有多少个。
看一下数据范围: \(h <= 3e3\), 这么小显然可以通过 数组下标 <-> 数值 的映射来处理, 数组元素的值为该数值出现的次数。即a[i] == i 出现的次数
代码
#include <iostream>
using namespace std;
const int N = 7e3 + 10;
int a[N];
int n, m;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
a[t]++;
m = max(m, t);
}
m *= 2;
int res = 0, ans = 0;
for (int i = 1; i <= m; i++)
{
int sum = 0;
for (int j = 1; j <= i / 2; j++)
{
if (j + j == i)
sum += a[j] / 2;
else
sum += min(a[j], a[i - j]);
}
if (sum == 0) continue;
if (res < sum)
{
res = sum;
ans = 1;
}
else if (res == sum)
ans++;
}
cout << res << " " << ans << endl;
return 0;
}