Linux下的ssh、scala、spark配置
A
題意
要你構造 \(n\) 個數,每個數的範圍是 \([1,4n]\),使得其中任意兩個數 \(a,b\) 不滿足 \(\gcd(a,b)=1\) 且不滿足 \(\gcd(a,b)=a\) 且不滿足 \(\gcd(a,b)=b\)
題解
一道一眼構造題。我們發現如果只取偶數的話就不可能出現 \((a,b)=1\) 的情況,而又他的範圍那麼大,所以如果取 \(2n+2,2n+4,...,4n\) 這 \(n\) 個數一定不會出現後兩種情況。這樣就構造完了。
#include <bits/stdc++.h> using namespace std; int t; int n; int main() { scanf("%d", &t); while (t--) { scanf("%d", &n); for (int i = 4 * n; i > 4 * n - 2 * n; i -= 2) { printf("%d ", i); } printf("\n"); } return 0; }
B
題意
給你兩個數 \(a,b\) 和一個長度為 \(n\) 的01字串。對於一個1的連通塊你需要花費 \(a\) 去炸掉它並且你可以花費 \(b\) 去把一個0變成1,求你炸掉所有1的最小花費是多少。
題解
從左往右推,把兩個1的連通塊之間的0的個數記為 \(x\),如果 \(x\times b<a\) 的話就把這一段填成1,答案加上 \(x \times b\),否則則需一段新的1連通塊,答案加上 \(a\)。注意一開始必須有一個1的連通塊,即如果存在1的話答案至少為 \(a\)。
#include <bits/stdc++.h> using namespace std; int t; int a, b; char s[100010]; int n; long long ans; int main() { scanf("%d", &t); while (t--) { ans = 0; scanf("%d%d", &a, &b); scanf("%s", s + 1); n = strlen(s + 1); bool have = false; int now = 0; for (int i = 1; i <= n; i++) { if (s[i] != s[i - 1]) { if (s[i] == '1') ans += have ? min(now * b, a) : a, have = true; now = 1; } else now++; } printf("%lld\n", ans); } return 0; }
C
題意
給你兩個數列:\(\{a\},\{b\}\),要你求出 \(\min_{P\subseteq \{1,2,...,n\}}\{\max\{\max_{i \in P}a_i,\sum_{i \notin P}b_i\}\}\)
題解
此題不需要二分答案,直接排序加列舉即可。我們發現在列舉 \(P\) 的時候如果對於一個 \(i \notin P\) 且 \(a_i \le \max_{i \in P}a_i\) 那這個 \(i\) 也包含進來一定更優。所以我們可以先對 \(a\) 排序,列舉前 \(i\) 個人是送外賣的,然後維護一個字尾和就可以了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll inf = 0x3f3f3f3f3f3f3f3f; struct node{ int a, b; friend bool operator < (node x, node y) { return x.a < y.a; } }Dish[200010]; int t; int n; ll sum, ans; int main() { scanf("%d", &t); while (t--) { ans = inf; sum = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &Dish[i].a); for (int i = 1; i <= n; i++) scanf("%d", &Dish[i].b); sort(Dish + 1, Dish + 1 + n); for (int i = n; i >= 1; i--) { ans = min(ans, max((ll)Dish[i].a, sum)); sum += Dish[i].b; } ans = min(ans, sum); printf("%lld\n", ans); } return 0; }
D
考慮一個位置至少有幾次操作是從最後到它這個位置的?如果 \(a_{i-1}<a_i\) 那麼顯然是 \(a_{i}-a_{i-1}\),那麼 \(a[i...n]\) 這一段數至少減 \(a_{i}-a_{i-1}\),如果把所有這些要減的加在一起,比某個 \(a_x\) 大了那麼就是NO
,否則就是YES
。
為什麼不用從後往前做一遍?看一下這張圖就懂了。
這一類題因為是區間同時減去某數所以有一段的差分陣列不變,多往差分上去想。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int q;
int n;
int a[100010];
int now;
int main() {
scanf("%d", &q);
while (q--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
now = 0;
bool ans = true;
for (int i = 1; i < n; i++) {
if (now > a[i]) {
ans = false;
break;
}
if (a[i] < a[i + 1]) now += a[i + 1] - a[i];
}
if (now > a[n]) ans = false;
if (ans) puts("YES");
else puts("NO");
}
return 0;
}
E
挺水的一題,可惜賽時不知道F更水,然後花了挺多時間搞這題害的最後沒時間寫F了/kk/kk/kk。
看到這道題讓你實現的就是跳到字典序比當前大一的排列上,而字典序最大為 \(2 \times 10^{10}\),這對於排列的個數來說其實是很小的。保守點說,排列變化的肯定是最後 \(20\) 個數。那麼對於前 \(n-20\) 個數維護一個字首和,後 \(20\) 個數暴力統計就可以做第一問。對於第二個操作,根據排名求排列有個逆康託展開的演算法,具體就是挨個確定每個位置是什麼數,如果暴力做的話複雜度上限是 \(20^3\),然而到不了所以這題可以過,如果要追求效率的話可以做到 \(20 \times \log^2{20}\) 或者 \(20 \times \log{20}\).
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, q;
ll now = 1;
ll a[200010], cnt[200010];
bool vis[200010];
ll ans;
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) a[i] = i, cnt[i] = a[i] + cnt[i - 1];
while (q--) {
int opt, l, r;
scanf("%d", &opt);
if (opt == 1) {
scanf("%d%d", &l, &r);
ans = 0;
if (n > 20) {
if (l < n - 20 && r >= n - 20) {
ans = cnt[n - 21] - cnt[l - 1];
l = n - 20;
for (int i = l; i <= r; i++) {
ans += a[i];
}
} else if (r < n - 20) {
ans = cnt[r] - cnt[l - 1];
} else {
for (int i = l; i <= r; i++) {
ans += a[i];
}
}
} else {
for (int i = l; i <= r; i++) {
ans += a[i];
}
}
printf("%lld\n", ans);
} else {
scanf("%d", &l);
now += l;
ll tmp = now;
ll cur = 1;
int pos;
for (pos = n; pos; pos--) {
if (cur >= now) {
break;
}
cur *= (n - pos + 1);
}
for (int i = pos + 1; i <= n; i++) vis[i] = false;
for (int i = pos + 1; i <= n; i++) {
cur /= (n - i + 1);
for (int j = pos + 1; j <= n; j++) {
if (vis[j]) continue;
ll num = 0;
for (int k = pos + 1; k < i; k++) {
if (a[k] < j) {
num++;
}
}
if (cur * (j - pos - num) >= now) {
now -= cur * (j - pos - num - 1);
a[i] = j;
vis[j] = true;
break;
}
}
}
now = tmp;
}
}
return 0;
}
F
首先我們發現要想某個數加入 \(b\) 就得把它旁邊兩個中的一個刪掉,然後這是兩種情況,所以每次操作最多兩種情況,答案肯定是 \(2\) 的倍數。考慮什麼情況不是 \(2\)?記錄每個數在 \(a\) 中出現的位置 \(p\),如果 \(a_{p_{b_i}+1}\) 是在 \(b\) 中 \(i\) 的後面出現的數,那麼一定不能刪。如果 \(p_{b_i}=n\) 那麼後面的數也沒得刪,前面同理。於是我們從後往前推,對於每個 \(b_i\) 打上標記,這樣就可以 \(O(1)\) 判斷了。
為什麼只有一開始的位置才影響答案?因為如果想要把旁邊的刪掉一定要把你這個位置的刪掉,而你這個位置在之前有不能刪,所以旁邊的一定還存在。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int inf = 0x3f3f3f3f;
int T;
int n, m;
int a[200010], b[200010], pos[200010];
bool vis[200010];
ll ans;
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), pos[a[i]] = i, vis[i] = false;
for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
ans = 1;
for (int i = m; i >= 1; i--) {
ll cur = 2;
if (pos[b[i]] == 1 || vis[a[pos[b[i]] - 1]]) cur--;
if (pos[b[i]] == n || vis[a[pos[b[i]] + 1]]) cur--;
ans = (ans * cur) % mod;
vis[b[i]] = true;
}
printf("%lld\n", ans);
}
return 0;
}