1. 程式人生 > 實用技巧 >Linux下的ssh、scala、spark配置

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;
}