Educational Codeforces Round 158 (Rated for Div. 2) A-D
A
大致题意: 有一条长度为x的直线公路,最开始你位于0号点并且此时你的油箱装满了油,公路有n个加油站,当你经过加油站的时候你可以在加油站加满油,每走一个单位需要花费1升油量,起始位置和终点没有加油站,请问你的油箱容量至少为多少升才可以够你跑一个来回。
解题思路: 我们的路径大致是这样0 -> a[1] -> a[2] -> ... a[n] -> x - > a[n] -> ... a[2] -> a[1] -> 0,观察发现除了a[n] -> n - > a[n]的时候这两段路程走完之前没有加油站其他的时候都有加油站,所以只需要算出其他路径的差的最大值和2 * (n - a[n])取一个max就是答案。
#include <bits/stdc++.h>
#define ll long long
#define db double
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
int d1[] = {0, 0, 1, -1};
int d2[] = {1, -1, 0, 0};
const int N = 2e5 + 10, M = N << 1;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
#define ls u << 1
#define rs u << 1 | 1
int n, m, k;
int a[N];
int main(void){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while(_ --) {
std::cin >> n >> m;
for (int i = 1; i <= n; i ++) std::cin >> a[i];
a[n + 1] = m;
int mn = 0;
for (int i = 1; i <= n; i ++)
mn = std::max(mn, a[i] - a[i - 1]);
mn = std::max(mn, a[n + 1] - a[n] + a[n + 1] - a[n]);
std::cout << mn << '\n';
}
return 0;
}
B
大致题意: 有一个长度为n的单元格,每个单元格上都有一个整数,最开始都是0. 一开始芯片在第一个位置,每当一个回合结束时(最开始也视为一个回合),芯片所在的单元格的整数+1.每回合结束时我们都有两个操作:
操作1:将芯片移动到下一个单元格(例如,如果芯片在 i单元格,则将其移动到 i + 1单元格)。如果芯片在最后一格,则无法进行此操作;
操作2:选择任意一个 x单元格,将芯片传送到该单元格。可以选择芯片当前所在的单元格;
每个单元格都有一个目标整数c[i]你的目标就是让每个单元格上的整数变为c[i],并且操作尽可能少的操作2,问最少使用多少次操作2可以让单元格上的整数都达成目标。
解题思路: 观察发现如果c[i] >= c[i + 1]那么每次使用操作2传送回 i 的时候能用操作1把格子i + 1也给走了。也就是说如果有这么一个连续子数组c[i] >= c[i + 1] >= c[i + 2] ...答案就是芯片传送回第i个格子的次数。可以将整个数组划分为若干段这样不上升的子数组。一旦c[i] < c[i + 1]就是新的一段的开始。需要注意的细节是我们在走c[i]的时候能把c[i + 1]也给走了,也就是新的一段需要减去上一段的最后一个元素。由于最开始就在第一个位置,所以需要将最开始的上一段置为1方便统计答案。
#include <bits/stdc++.h>
#define ll long long
#define db double
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
int d1[] = {0, 0, 1, -1};
int d2[] = {1, -1, 0, 0};
const int N = 2e5 + 10, M = N << 1;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
#define ls u << 1
#define rs u << 1 | 1
int n, m, k;
int a[N];
int main(void){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while(_ --) {
std::cin >> n;
for (int i = 1; i <= n; i ++) std::cin >> a[i];
ll ans = 0;
int i = 1, j = 2;
int last = 1;
for (; j <= n; j ++) {
if (a[j] > a[j - 1]) {
ans += a[i] - last;
i = j;
last = a[j - 1];
}
}
ans += a[i] - last;
std::cout << ans << '\n';
}
return 0;
}
C
大致题意: 有一个长度为n的数组,你可以进行无数次以下操作:将数组上每个数字加上x(0 <= x <= 1e18)然后除以2(下去整),问最少要多少次可以让数组所有元素相等。
解题思路: 我们关注两个元素t1, t2(t1 < t2)。
如果t2 - t1 == 1的话我们只需要操作一次就可以得到答案:1.若t1为偶数:让t1和t2除以2一次就可以让他们两个一样。2.如果t1为奇数:需要先让t1和t2加上1再除以2就可以使他们两个一样。
如果t2 - t1 > 1,就变成了如何让(t2 + x) / 2 - (t1 + x) / 2差值更小,此时会发现如果t1为奇数且t2为偶数那么x = 1会让差值更小(写题的时候只需要判断t1是否为奇数即可,就算t2也是奇数那么都加上1之后再除以2这两者的差值大小也是不会变的)。如果t1是偶数t2是奇数x等于会导致差值变大,所以当t1是偶数的时候x设为0就可以了。在这个过程中发现无论如何t2的不可能变的比t1小,t1不可能变的比t2大,所以只取数组中的最大值和最小值,只要它们两个变的一样了那么其他所有数字一定和他们两个一样。
#include <bits/stdc++.h>
#define ll long long
#define db double
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
int d1[] = {0, 0, 1, -1};
int d2[] = {1, -1, 0, 0};
const int N = 2e5 + 10, M = N << 1;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
#define ls u << 1
#define rs u << 1 | 1
int n, m, k;
int a[N];
int main(void){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while(_ --) {
std::cin >> n;
for (int i = 1; i <= n; i ++) std::cin >> a[i];
std::sort(a + 1, a + n + 1);
int mn = a[1], mx = a[n];
int ans = 0;
std::vector<int> tmp;
while (mn < mx) {
if (mx - mn == 1) {
if (mn % 2 == 1) tmp.push_back(1);
else tmp.push_back(0);
break;
}
if (mn % 2 == 1) {
mn = (mn + 1) / 2;
mx = (mx + 1) / 2;
tmp.push_back(1);
} else {
mn = mn / 2;
mx = mx / 2;
tmp.push_back(0);
}
}
std::cout << tmp.size() << '\n';
if (tmp.size() <= n && tmp.size()) {
for (int i = 0; i < tmp.size(); i ++) std::cout << tmp[i] << ' ';
std::cout << '\n';
}
}
return 0;
}
D
大致题意:给你一个整数n和n个怪的血量,你是一个强大的魔法师可以选择攻击任意一个怪对他造成x的伤害(只能释放一次),然后你的攻击会随机选择相邻的怪并对其造成x - 1的伤害,现在有两个怪被攻击了,接下来会随机选择攻击和这两个怪相邻的怪(每个怪只会被攻击一次)并造成x - 2的伤害,以此类推。一旦怪物受到的伤害大于等于它的血量他就会死掉,询问你最少需要使用多少伤害的魔法(x)才能将所有的怪杀死。请注意,你选择的是初始目标和法术威力,其他事情应视为随机,你希望即使在最坏的情况下也能杀死所有怪物。
解题思路:考虑枚举每个位置作为攻击目标时需要的最少魔法伤害是多少。考虑从左往右枚举,首先预处理一个b数组让每个值表示从1号点开始释放魔法x至少需要b[i]的伤害第i个怪才能被消灭,因为1号点只能往右边依次进行攻击,所以让b[i] = a[i] + i - 1就是每个怪被从1号怪释放的攻击打死的最小值。再维护一个right数组表示右边的后缀最大值是多少。同理预处理一个c数组让每个值表示从n号点开始释放魔法x至少需要c[i]的伤害第i个怪才能被消灭,再维护一个left数组表示左边的前缀最大值是多少。
那么1号怪释放攻击所需要的最少魔法伤害就是max(a[1], right[2])。当枚举的位置移动到2号怪的时候2号怪右边的所有怪对2号点的贡献就是r = right[3] - 1, 2号怪左边的怪对2号点的贡献就是l = left[1] - (n - 2)(因为left[1]是到n的魔法值,现在是求到2号点所以需要减去2到n的距离)。我们得到了l和r之后只需要考虑最坏情况即可,对于左侧的最坏情况就是先把右侧攻击完了再攻击左侧,对于右侧的最坏情况就是先把左侧攻击完了再攻击右边, 再把本身带入进来比较一下就是答案。同理可以递推到n号怪。
#include <bits/stdc++.h>
#define ll long long
#define db double
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
int d1[] = {0, 0, 1, -1};
int d2[] = {1, -1, 0, 0};
const int N = 3e5 + 10, M = N << 1;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
#define ls u << 1
#define rs u << 1 | 1
int n, m, k;
int a[N], b[N];
int left[N], right[N];
signed main(void){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
//std::cin >> _;
while(_ --) {
std::cin >> n;
for (int i = 1; i <= n; i ++) std::cin >> a[i];
//预处理右侧到1号点的价值
for (int i = 1; i <= n; i ++) b[i] = a[i] + i - 1;
//预处理前缀和
for (int i = n; i; i --) right[i] = std::max(right[i + 1], b[i]);
//预处理左侧到n号点的价值
for (int i = 1; i <= n; i ++) b[i] = a[i] + n - i;
//预处理前缀和
for (int i = 1; i <= n; i ++) left[i] = std::max(b[i], left[i - 1]);
int ans = 2e9;
for (int i = 1; i <= n; i ++) {
int l = -INF, r = -INF;
if (i != 1) l = left[i - 1] - (n - i);
if (i + 1 <= n) r = right[i + 1] - i + 1;
ans = std::min(ans, std::max({l + n - i, r + i - 1, a[i]}));
}
std::cout << ans << '\n';
}
return 0;
}