【CodeForces】Ozon Tech Challenge 2020

比賽鏈接

點擊打開鏈接

官方題解

點擊打開鏈接

Problem A. Kuroni and the Gifts

aia_ibib_i 排序後輸出即可。

時間複雜度 O(TNLogN)O(TNLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int a[MAXN], b[MAXN];
int main() {
	int T; read(T);
	while (T--) {
		int n; read(n);
		for (int i = 1; i <= n; i++)
			read(a[i]);
		for (int i = 1; i <= n; i++)
			read(b[i]);
		sort(a + 1, a + n + 1);
		sort(b + 1, b + n + 1);
		for (int i = 1; i <= n; i++)
			printf("%d ", a[i]);
		printf("\n");
		for (int i = 1; i <= n; i++)
			printf("%d ", b[i]);
		printf("\n");
	}
	return 0;
}

Problem B. Kuroni and Simple Strings

最終剩餘的串一定可以找到一個分界點,使得其前面都是 )) ,後面都是 ((
因此,若當前字符串以 )) 開頭,或以 (( 結尾,顯然可以刪除之。
否則,字符串的開頭和結尾將構成一對匹配的括號,需要對其進行操作。

由此,我們可以看到,若需要進行操作,則至多需要進行一次操作。

時間複雜度 O(S)O(|S|)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool vis[MAXN];
char s[MAXN];
int main() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	int l = 1, r = n, k = 0;
	while (l <= r) {
		if (s[l] == ')') l++;
		else if (s[r] == '(') r--;
		else {
			vis[l] = vis[r] = true;
			k++, l++, r--;
		}
	}
	if (k) {
		printf("%d\n%d\n", 1, 2 * k);
		for (int i = 1; i <= n; i++)
			if (vis[i]) printf("%d ", i);
		printf("\n");
	} else printf("%d\n", 0);
	return 0;
}

Problem C. Kuroni and Impossible Calculation

N>MN>M ,根據抽屜原理,必然存在兩個同餘的數,則答案爲 00
否則,有 N1000N\leq 1000 ,可以暴力計算答案。

時間複雜度 O(N+M2)O(N+M^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, P, a[MAXN];
int main() {
	read(n), read(P);
	if (n > P) puts("0");
	else {
		int ans = 1;
		for (int i = 1; i <= n; i++)
			read(a[i]);
		sort(a + 1, a + n + 1);
		for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++)
			ans = 1ll * ans * (a[j] - a[i]) % P;
		cout << ans << endl;
	}
	return 0;
}

Problem D. Kuroni and the Celebration

通過一次詢問,我們可以得到樹上一條路徑上深度最小的點。

那麼,考慮詢問一對不同的葉子節點 x,yx,y
若得到的回答是 x,yx,y 中的一者,則可以直接確定根節點。
否則,令答案爲 zz ,我們可以確定根節點不在 x,yx,y 所在的 zz 的子樹中。因此,可以刪去這兩個子樹,在剩餘的樹上重複這一過程。一次詢問將導致候選點集的大小至少 2-2

時間複雜度 O(N2)O(N^2) ,使用操作 N2\lfloor\frac{N}{2}\rfloor 次。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool res[MAXN]; int vis[MAXN], task;
int n; vector <int> a[MAXN];
void erase(vector <int> &a, int x) {
	for (unsigned i = 0; i < a.size(); i++)
		if (a[i] == x) {
			swap(a[i], a[a.size() - 1]);
			a.pop_back();
			return;
		}
}
int cntres() {
	int cnt = 0;
	for (int i = 1; i <= n; i++)
		cnt += res[i];
	return cnt;
}
void col(int pos, int fa) {
	vis[pos] = task;
	for (auto x : a[pos])
		if (x != fa) col(x, pos);
}
void dres(int pos, int fa) {
	res[pos] = false;
	for (auto x : a[pos])
		if (x != fa) dres(x, pos);
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		res[i] = true;
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	while (cntres() != 1) {
		int x = 0, y = 0;
		for (int i = 1; i <= n; i++)
			if (res[i] && a[i].size() == 1) {
				y = x;
				x = i;
			}
		assert(x != 0 && y != 0);
		cout << '?' << ' ' << x << ' ' << y << endl;
		int z, rx = 0, ry = 0; read(z);
		if (z == x || z == y) {
			cout << '!' << ' ' << z << endl;
			return 0;
		}
		for (auto p : a[z]) {
			task++;
			col(p, z);
			if (vis[x] == task) rx = p;
			if (vis[y] == task) ry = p;
		}
		erase(a[z], rx);
		erase(a[z], ry);
		dres(rx, z), dres(ry, z);
	}
	for (int i = 1; i <= n; i++)
		if (res[i] == true) {
			cout << '!' << ' ' << i << endl;
			return 0;
		}
	return 0;
}

Problem E. Kuroni and the Score Distribution

考慮滿足 ai+aj=ak,i<j<ka_i+a_j=a_k,i<j<k 的三元組 (i,j,k)(i,j,k)
對於固定的 kk ,至多能夠產生 k12\lfloor\frac{k-1}{2}\rfloor 個三元組。並且,若令 ai=ia_i=i ,這個上界對每一個 kk 均可取到。因此,令 Max=k=1Nk12Max=\sum_{k=1}^{N}\lfloor\frac{k-1}{2}\rfloor ,當 MMax+1M\geq Max+1 ,問題無解。

Max=MMax=M ,則可以直接令 ai=ia_i=i
否則,一定存在最小的 pospos ,使得 k=1posk12>M\sum_{k=1}^{pos}\lfloor\frac{k-1}{2}\rfloor>M

考慮按照如下方式構造:
ai={ii<pos2i1+2(Mk=1pos1k12)i=pos5×108+2×104×ii>pos a_i=\left\{\begin{array}{rcl}i&&{i<pos}\\2i-1+2(M-\sum_{k=1}^{pos-1}\lfloor\frac{k-1}{2}\rfloor) & & {i=pos}\\5\times 10^8+2\times 10^4\times i & & {i>pos}\end{array} \right.

可以保證 a1,a2,,aposa_1,a_2,\dots,a_pos 產生了 MM 個三元組,並且剩餘 aia_i 不能夠產生三元組。

時間複雜度 O(N)O(N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, a[MAXN];
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		a[i] = 5e8 + 2e4 * i;
	for (int i = 1; i <= n; i++) {
		if ((i - 1) / 2 <= m) {
			a[i] = i;
			m -= (i - 1) / 2;
		} else {
			a[i] = i - 1 + (i - 2 * m);
			m = 0;
			break;
		}
	}
	if (m > 0) {
		puts("-1");
		return 0;
	}
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
	printf("\n");
	return 0;
}

Problem F. Kuroni and the Punishment

若固定所有數最終的一個公約數 gg ,顯然可以通過 O(N)O(N) 貪心求出最少步數。

考慮取 g=2g=2 ,則可以發現,操作次數不超過 NN ,因此答案在 NN 以內。
這表明,在最優方案中,有至少一半的數被操作的次數在 11 以內。

由此,可以考慮隨機一個數 xx ,令 ggx1,x,x+1x-1,x,x+1 中所有出現過的質因子更新答案。
若隨機 TT 次,可以保證正確率在 10.5T1-0.5^T 以上。

時間複雜度 O(T(V+N))O(T(\sqrt{V}+N)) ,其中 TT 爲迭代次數。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n; ll a[MAXN];
int calc(ll g) {
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		if (a[i] < g) ans += g - a[i];
		else {
			ll tmp = a[i] % g;
			ans += min(tmp, g - tmp);
		}
	}
	if (ans > n) return n;
	else return ans;
}
int work(ll tmp) {
	int ans = n;
	if (tmp == 0) return n;
	for (int i = 2; 1ll * i * i <= tmp; i++)
		while (tmp % i == 0) {
			tmp /= i;
			chkmin(ans, calc(i));
		}
	if (tmp != 1) chkmin(ans, calc(tmp));
	return ans;
}
int main() {
	read(n);
	srand('X' + 'Y' + 'X');
	for (int i = 1; i <= n; i++)
		read(a[i]);
	random_shuffle(a + 1, a + n + 1);
	int ans = n;
	for (int i = 1; i <= 40; i++) {
		int pos = ((rand() << 15) + rand()) % n + 1;
		chkmin(ans, work(a[pos]));
		chkmin(ans, work(a[pos] + 1));
		chkmin(ans, work(a[pos] - 1));
	}
	cout << ans << endl;
	return 0;
}

Problem G. Kuroni and Antihype

新增一個權值爲 00 的人,令自行加入的人是此人所邀請的。

拋開連邊方式,考慮如下子問題:

給定一張 NN 個點的有向圖,其中一個點是源點,與所有點之間均有邊,需要選擇權值儘量大的 N1N-1 條邊,使得源點可以通過所選的邊到達所有點。
若不考慮圖的特殊性,這個問題必須轉化爲最小樹形圖來解決。

本題中,這張有向圖具有一定特殊性:
(1)(1) 、若存在邊 iji\rightarrow j ,則一定存在邊 jij\rightarrow i
(2)(2) 、邊 iji\rightarrow j 的權值爲 aia_i

注意到最終形成的樹形圖上,每個點的入度均爲 11 ,因此,可以將邊權更改爲 ai+aja_i+a_j ,在最終答案中減去 ai\sum a_i 。此時,問題被轉化爲了無向圖最小生成樹的問題。

那麼,用並查集維護連通性,通過枚舉子集從邊權大到小加入所有邊即可。

時間複雜度 O(318)O(3^{18})

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 18;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
bool vis[MAXN]; ll ans;
int n, cnt[MAXN], f[MAXN];
int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
void work(int x, int y) {
	ll inc = x + y, cmt = 1;
	x = find(x), y = find(y);
	if (x == y) return;
	if (!vis[x]) cmt += cnt[x] - 1;
	if (!vis[y]) cmt += cnt[y] - 1;
	ans += inc * cmt, f[x] = y;
	vis[x] = vis[y] = true;
}
int main() {
	read(n), cnt[0]++;
	for (int i = 1; i <= n; i++) {
		int x; read(x);
		cnt[x]++, ans -= x;
	}
	int u = (1 << 18) - 1;
	for (int i = 0; i <= u; i++)
		f[i] = i, vis[i] = false;
	for (int i = u; i >= 0; i--)
	for (int j = i; j > (i ^ j); j = (j - 1) & i)
		if (cnt[j] && cnt[i ^ j]) work(j, i ^ j);
	cout << ans << endl;
	return 0;
}

Problem H. Kuroni the Private Tutor

考慮固定學生的得分 ai(aiai1)a_i(a_i\geq a_{i-1}) ,判斷該得分分佈是否可能出現。

不計時間複雜度,我們可以用有上下界的網絡流進行判斷。
考慮構造一個左側 NN 個點,右側 MM 個點的完全二分圖,各邊流量限制爲 11 。源點連向左側第 ii 個點,流量下界爲 lil_i ,上界爲 rir_i 。右側第 ii 個點連向匯點,流量限制爲 aia_i 。那麼,該得分分佈可能出現,當且僅當存在流量大小爲 ai=T\sum a_i=T 的可行流。

由於上述二分圖構造規律顯著,可以考慮用最大流最小割定理對存在可行流的條件加以分析。
ai,li,ria_i,l_i,r_i 均爲有序數組, sai,sli,srisa_i,sl_i,sr_i 分別爲它們的前綴和。
則存在可行流當且僅當對於任意的 i[0,N],j[0,M]i\in[0,N],j\in[0,M] ,以下兩個條件均成立:
(1)(1)sli+saj+(Ni)×(Mj)saMsl_i+sa_j+(N-i)\times (M-j)\geq sa_M
(2)(2)sri+saj+(Ni)×(Mj)Tsr_i+sa_j+(N-i)\times (M-j)\geq T

考慮從小到大枚舉 ii ,那麼,使得不等式左側取到最小值的 jj 是單調不增的,可以方便地用雙指針在 O(N+M)O(N+M) 的時間內進行判斷。

由上面的分析,我們也可以發現,我們希望前綴和數組 saisa_i 中的元素儘可能大。
根據給定條件,我們可以確定一個所有 aia_i 的下界,同時,還會剩餘一些能夠分配的分數。此時,我們會希望將分數儘可能分配給靠前的 aia_i ,使得前綴和數組 saisa_i 中的元素儘可能大。

兩個答案均可以二分,二分後構造合適的 aia_i 判斷是否合法即可。

時間複雜度 O((N+M)Log(N+M))O((N+M)Log(N+M))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, m, q, l[MAXN], r[MAXN], x[MAXN];
int a[MAXN]; ll t, sa[MAXN], sl[MAXN], sr[MAXN];
bool check() {
	for (int i = 1; i <= m; i++)
		sa[i] = sa[i - 1] + a[i];
	for (int i = 0, j = m; i <= n; i++) {
		while (j >= 1 && a[j] >= n - i) j--;
		if (sl[i] + sa[j] + 1ll * (n - i) * (m - j) < sl[n]) return false;
		if (sr[i] + sa[j] + 1ll * (n - i) * (m - j) < t) return false;
	}
	return true;
}
bool check(int cnt, int val) {
	ll sum = 0; bool flg = true;
	for (int i = 1; i <= m; i++) {
		a[i] = max(a[i - 1], x[i]), sum += a[i];
		if (m - i + 1 <= cnt) flg &= x[i] == -1;
	}
	if (!flg) {
		if (a[m] < val) return false;
		for (int i = 1; i <= cnt; i++) {
			if (x[m - i + 1] != -1 && a[m - i + 1] != a[m]) return false;
			sum += a[m] - a[m - i + 1];
			a[m - i + 1] = a[m];
		}
	} else {
		for (int i = 1; i <= cnt; i++) {
			if (a[m - i + 1] < val) {
				sum += val - a[m - i + 1];
				a[m - i + 1] = val;
			}
		}
	}
	if (sum > t) return false;
	sum = t - sum; ll bak = sum;
	for (int i = 1, last = 0; i <= m; i++)
		if (x[i] != -1 || m - i + 1 <= cnt) {
			int rng = i - last - 1, inc = a[i] - a[last];
			if (1ll * inc * rng <= sum) {
				sum -= 1ll * inc * rng;
				for (int j = last + 1; j <= i - 1; j++)
					a[j] += inc;
			} else {
				inc = sum / rng, sum -= 1ll * inc * rng;
				for (int j = last + 1; j <= i - 1; j++) {
					a[j] += inc;
					if (i - j <= sum) a[j]++;
				} sum = 0;
				break;
			}
			last = i;
		}
	if (!flg) {
		if (sum != 0) return false;
	} else {
		if (sum != 0) {
			int tmp = cnt;
			while (tmp < m && x[m - tmp] == -1) tmp++;
			ll inc = (sum - 1) / tmp + 1;
			for (int i = 1; i <= m; i++) {
				a[i] = max(a[i - 1], x[i]);
				if (m - i + 1 <= cnt) chkmax(a[i], val);
			}
			if (a[m] + inc > n) return false;
			for (int i = 1; i <= cnt; i++)
				a[m - i + 1] += inc;
			sum = bak - inc * cnt;
			if (sum < 0) return false;
			for (int i = 1, last = 0; i <= m; i++)
				if (x[i] != -1 || m - i + 1 <= cnt) {
					int rng = i - last - 1, inc = a[i] - a[last];
					if (1ll * inc * rng <= sum) {
						sum -= 1ll * inc * rng;
						for (int j = last + 1; j <= i - 1; j++)
							a[j] += inc;
					} else {
						inc = sum / rng, sum -= 1ll * inc * rng;
						for (int j = last + 1; j <= i - 1; j++) {
							a[j] += inc;
							if (i - j <= sum) a[j]++;
						} sum = 0;
						break;
					}
					last = i;
				}
			if (sum != 0) return false;
		}
	}
	return check();
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(l[i]), read(r[i]);
	sort(l + 1, l + n + 1);
	sort(r + 1, r + n + 1);
	for (int i = 1; i <= n; i++) {
		sl[i] = sl[i - 1] + l[i];
		sr[i] = sr[i - 1] + r[i];
	}
	for (int i = 1; i <= m; i++)
		x[i] = -1;
	read(q);
	for (int i = 1; i <= q; i++) {
		int pos; read(pos);
		read(x[m - pos + 1]);
	}
	read(t);
	if (!check(1, 0)) {
		puts("-1 -1");
		return 0;
	}
	int l = 1, r = m;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (check(mid, 0)) l = mid;
		else r = mid - 1;
	}
	int cnt = l; l = 0, r = n;
	cout << cnt << ' ';
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (check(cnt, mid)) l = mid;
		else r = mid - 1;
	}
	cout << l << endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章