A题 Select Three Sticks(签到)
给定 \(n\) 根木棒,第 \(i\) 根木棒的长度为 \(a_i\)。现在我们可以进行操作,每次操作选定一根木棒,将其长度增高或减少 1。问至少需要进行多少次操作,才能使得我们能够选出 3 根木棒,并拼成一个等边三角形?
\(T\leq 100,n\leq 300,1\leq a_i\leq 10^9\)
显然我们只会对这三根木棒进行操作,所以我们只需要枚举这三根木棒,就能 \(O(1)\) 的检查需要多少次操作(看最终长度变成啥就行,比赛后发现变成中间长度那个是最优的)。
直接 \(O(n^3)\) 枚举肯定不行(虽然我觉得 A 题应该把这个放过去),所以我们考虑优化:显然,排序后只需要取相邻三个即可(不相邻的肯定比相邻的劣),这样就把复杂度变成 \(O(n\log n)\) 了。
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
int solve() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + n + 1);
int res = 2e9;
for (int i = 3; i <= n; ++i) {
int x = a[i - 2], y = a[i];
res = min(res, y - x);
}
return res;
}
int main() {
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
B题 Bright, Nice, Brilliant(构造)
建议直接看原题。
(看了题解后)发现,第 \(i\) 层的亮度至多为 \(i\)(就算把前 \(i-1\) 层的灯都点上,\((i,1)\) 收到的亮度也就是 \(i-1\),加上自己的灯就是 \(i\))。
观察样例,发现一种构造方案:只点亮最两边的房间,投影法发现这样可以符合要求。
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
if (i == 1) puts("1");
else {
printf("1");
for (int j = 2; j < i; ++j) printf(" 0");
puts(" 1");
}
}
int main() {
int T;
cin >> T;
while (T--) solve();
return 0;
}
C题 Removing Smallest Multiples(贪心,数学)
给定集合 \(S=\{1,2,3,\cdots,n\}\),现在我们可以进行多次操作,每次操作选定一个正整数 \(k\),然后删除 \(S\) 里面的最小的 \(k\) 的倍数(包括 \(k\) 自己)。问至少需要删多少次,才能使得 \(S\) 变成集合 \(T\) ?(\(T\) 是给定的一个 \(S\) 的子集)
\(T\leq 10^4,1\leq n,\sum n\leq 10^6\)
要删掉某个数,应该选一个尽可能小的因数,并且这个因数的相对较小的倍数(比这个数小的)都要删。
那么我们直接贪心(或者说有点类似素数筛),从小到大枚举 \(x\),看 \(x\) 能删掉哪些数(碰到一个不能删的就 break 掉,否则判断这个数是不是之前被删掉,没被删掉的话就用 \(x\) 来删就行了),复杂度大概最多 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1000010;
int n, a[N];
char s[N];
LL solve() {
scanf("%d%s", &n, s + 1);
memset(a, 0, sizeof(int) * (n + 1));
LL ans = 0;
for (int i = 1; i <= n; ++i)
if (s[i] == '0') {
for (int j = i; j <= n; j += i)
if (s[j] == '0') {
if (a[j] == 0) ans += i, a[j] = 1;
}
else break;
}
return ans;
}
int main() {
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
D题 Slime Escape(贪心)
给定一个数轴,位置下标从 1 到 n,第 \(i\) 个位置有一个血量为 \(a_i\) 的史莱姆。现在我们要控制位置在 \(k\) 处的史莱姆,通过左右跳动的方式到达位置 \(0\) 或者 \(n+1\)。
我们的史莱姆可以左右跳动(每次一格),当目标位置有一个史莱姆的时候就吃掉它,然后自身的血量就加上它的血量。但是注意,有的史莱姆的血量是负数,这意味着吃掉它后我们的血量会变少。当血量变成负数时,我们的史莱姆就 G 了,我们不希望这种情况发生。
问:我们能否到达目标位置?
\(T\leq 2*10^4,3\leq n,\sum n\leq 2*10^5,-10^9\leq a_i\leq 10^9,a_k\geq 0\)
有这样一种思路:我们首先判断能不能向右到达终点,达不到的话就向右取一个最大子段和,吃完后看看能不能向左到达终点,达不到的话就再向左取一个最大字段和,然后再向右,持续下去,直到到达终点。至于无解,就这样判断:发现当前状态下,无论是向左还是向右都无法到达终点,且最大子段和也不再存在(不可能通过吃一段史莱姆的方式来增加血量了),这时输出无解即可。
这样反复走的时间似乎有点高,但实际上它是 \(O(n)\) 的:因为我们记录一下已经吃掉的史莱姆的范围,每次只从这个范围的左右端点扩展,类似两个指针往外拓,每个史莱姆至多吃一次,所以总复杂度是 \(O(n)\) 的。
思路其实还好,主要是写起来烦,还要记录状态啥的。
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
int n, k; LL a[N];
struct Node { int farPos, vpos; LL x; };
Node getR(LL x, int p) {
LL s, maxv; int farPos, vpos;
s = x, farPos = n;
for (int i = p + 1; i <= n; ++i)
if ((s += a[i]) < 0) { farPos = i - 1; break; }
maxv = x, vpos = p, s = x;
for (int i = p + 1; i <= farPos; ++i)
if ((s += a[i]) > maxv) maxv = s, vpos = i;
return (Node){farPos, vpos, maxv};
}
Node getL(LL x, int p) {
LL s, maxv; int farPos, vpos;
s = x, farPos = 1;
for (int i = p - 1; i >= 1; --i)
if ((s += a[i]) < 0) { farPos = i + 1; break; }
maxv = x, vpos = p, s = x;
for (int i = p - 1; i >= farPos; --i)
if ((s += a[i]) > maxv) maxv = s, vpos = i;
return (Node){farPos, vpos, maxv};
}
//
bool solve() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
int l = k, r = k; LL x = a[k];
while (true) {
Node R = getR(x, r); x = R.x;
if (R.farPos == n) return true;
Node L = getL(x, l); x = L.x;
if (L.farPos == 1) return true;
if (l == L.vpos && r == R.vpos) return false;
l = L.vpos, r = R.vpos;
}
return true;
}
int main() {
int T;
scanf("%d", &T);
while (T--) puts(solve() ? "YES" : "NO");
return 0;
}
标签:farPos,int,题解,LL,maxv,CF,史莱姆,leq,822
From: https://www.cnblogs.com/cyhforlight/p/16725548.html