【CodeForces】Ozon Tech Challenge 2020

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Kuroni and the Gifts

a i a_i b i b_i 排序后输出即可。

时间复杂度 O ( T N L o g N ) 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 > M N>M ,根据抽屉原理,必然存在两个同余的数,则答案为 0 0
否则,有 N 1000 N\leq 1000 ,可以暴力计算答案。

时间复杂度 O ( N + M 2 ) 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 , y x,y
若得到的回答是 x , y x,y 中的一者,则可以直接确定根节点。
否则,令答案为 z z ,我们可以确定根节点不在 x , y x,y 所在的 z z 的子树中。因此,可以删去这两个子树,在剩余的树上重复这一过程。一次询问将导致候选点集的大小至少 2 -2

时间复杂度 O ( N 2 ) O(N^2) ,使用操作 N 2 \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

考虑满足 a i + a j = a k , i < j < k a_i+a_j=a_k,i<j<k 的三元组 ( i , j , k ) (i,j,k)
对于固定的 k k ,至多能够产生 k 1 2 \lfloor\frac{k-1}{2}\rfloor 个三元组。并且,若令 a i = i a_i=i ,这个上界对每一个 k k 均可取到。因此,令 M a x = k = 1 N k 1 2 Max=\sum_{k=1}^{N}\lfloor\frac{k-1}{2}\rfloor ,当 M M a x + 1 M\geq Max+1 ,问题无解。

M a x = M Max=M ,则可以直接令 a i = i a_i=i
否则,一定存在最小的 p o s pos ,使得 k = 1 p o s k 1 2 > M \sum_{k=1}^{pos}\lfloor\frac{k-1}{2}\rfloor>M

考虑按照如下方式构造:
a i = { i i < p o s 2 i 1 + 2 ( M k = 1 p o s 1 k 1 2 ) i = p o s 5 × 1 0 8 + 2 × 1 0 4 × i i > p o s 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.

可以保证 a 1 , a 2 , , a p o s a_1,a_2,\dots,a_pos 产生了 M M 个三元组,并且剩余 a i a_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

若固定所有数最终的一个公约数 g g ,显然可以通过 O ( N ) O(N) 贪心求出最少步数。

考虑取 g = 2 g=2 ,则可以发现,操作次数不超过 N N ,因此答案在 N N 以内。
这表明,在最优方案中,有至少一半的数被操作的次数在 1 1 以内。

由此,可以考虑随机一个数 x x ,令 g g x 1 , x , x + 1 x-1,x,x+1 中所有出现过的质因子更新答案。
若随机 T T 次,可以保证正确率在 1 0. 5 T 1-0.5^T 以上。

时间复杂度 O ( T ( V + N ) ) O(T(\sqrt{V}+N)) ,其中 T T 为迭代次数。

#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

新增一个权值为 0 0 的人,令自行加入的人是此人所邀请的。

抛开连边方式,考虑如下子问题:

给定一张 N N 个点的有向图,其中一个点是源点,与所有点之间均有边,需要选择权值尽量大的 N 1 N-1 条边,使得源点可以通过所选的边到达所有点。
若不考虑图的特殊性,这个问题必须转化为最小树形图来解决。

本题中,这张有向图具有一定特殊性:
( 1 ) (1) 、若存在边 i j i\rightarrow j ,则一定存在边 j i j\rightarrow i
( 2 ) (2) 、边 i j i\rightarrow j 的权值为 a i a_i

注意到最终形成的树形图上,每个点的入度均为 1 1 ,因此,可以将边权更改为 a i + a j a_i+a_j ,在最终答案中减去 a i \sum a_i 。此时,问题被转化为了无向图最小生成树的问题。

那么,用并查集维护连通性,通过枚举子集从边权大到小加入所有边即可。

时间复杂度 O ( 3 18 ) 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

考虑固定学生的得分 a i ( a i a i 1 ) a_i(a_i\geq a_{i-1}) ,判断该得分分布是否可能出现。

不计时间复杂度,我们可以用有上下界的网络流进行判断。
考虑构造一个左侧 N N 个点,右侧 M M 个点的完全二分图,各边流量限制为 1 1 。源点连向左侧第 i i 个点,流量下界为 l i l_i ,上界为 r i r_i 。右侧第 i i 个点连向汇点,流量限制为 a i a_i 。那么,该得分分布可能出现,当且仅当存在流量大小为 a i = T \sum a_i=T 的可行流。

由于上述二分图构造规律显著,可以考虑用最大流最小割定理对存在可行流的条件加以分析。
a i , l i , r i a_i,l_i,r_i 均为有序数组, s a i , s l i , s r i sa_i,sl_i,sr_i 分别为它们的前缀和。
则存在可行流当且仅当对于任意的 i [ 0 , N ] , j [ 0 , M ] i\in[0,N],j\in[0,M] ,以下两个条件均成立:
( 1 ) (1) s l i + s a j + ( N i ) × ( M j ) s a M sl_i+sa_j+(N-i)\times (M-j)\geq sa_M
( 2 ) (2) s r i + s a j + ( N i ) × ( M j ) T sr_i+sa_j+(N-i)\times (M-j)\geq T

考虑从小到大枚举 i i ,那么,使得不等式左侧取到最小值的 j j 是单调不增的,可以方便地用双指针在 O ( N + M ) O(N+M) 的时间内进行判断。

由上面的分析,我们也可以发现,我们希望前缀和数组 s a i sa_i 中的元素尽可能大。
根据给定条件,我们可以确定一个所有 a i a_i 的下界,同时,还会剩余一些能够分配的分数。此时,我们会希望将分数尽可能分配给靠前的 a i a_i ,使得前缀和数组 s a i sa_i 中的元素尽可能大。

两个答案均可以二分,二分后构造合适的 a i a_i 判断是否合法即可。

时间复杂度 O ( ( N + M ) L o g ( 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;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/104844429