时隔两年再次VP一场Codeforces Div. 2,一万八参赛,赛时(VP)排名71(第一次进前一百竟然是时隔两年的VP)

A. Zhan's Blender

简单题。

#include<bits/stdc++.h>
using namespace std;
int T, n, x, y;
 
int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d%d%d", &n, &x, &y);
		int t = min(x, y);
		printf("%d\n", (n + t - 1) / t);
	}
	return 0;
}

B. Battle for Survive

由于 ai 可以为负数,因此问题答案可以看成:求序列中每个数进行加减得到最大的数是多少?

由于所有数初始全为正,因此我们想让尽可能多,尽可能大的数加进答案,而不是进行减法。

由于顺序,倒数第二个数一定是被减去的,而其他数可以先与倒数第二个数打架,然后由倒数第二个数传导到倒数第一个数,变成正的。

因此答案为所有数的和减去倒数第二个数的两倍

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int T, n, a[N];
 
int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		long long sum = 0;
		for(int i = 1; i <= n; i ++) {
			scanf("%d", &a[i]);
			sum += a[i];
		}
		sum -= a[n - 1] * 2;
		printf("%lld\n", sum);
	}
	return 0;
}

C. Password Cracking

第一次询问是否有 1 ,没有则为全 0 ,否则初始子串为 "1" 。

后面每次分别往字串后面添加 '0' 和 '1' ,询问两次,若有相同的,则子串长度加 1。

若往后加 '0' 和 '1' 都没有相同的,代表当前串是原字符串的后缀。

这时尝试往前加 '1' ,相同则加 '1' ,不同则加 '0'。

可以在 2n 次范围内询问出答案。

#include<bits/stdc++.h>
using namespace std;
int T, n, f;

int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		cout << "? 1" << '\n';
		cout.flush();
		scanf("%d", &f);
		if(f == 0) {
			string ans = "";
			for(int i = 1; i <= n; i ++)
				ans += "0";
			cout << "! " << ans << '\n';
			cout.flush();
			continue;
		}
		string ans = "1"; bool flag = 0;
		for(int i = 2; i <= n; i ++) {
			if(!flag) {
				cout << "? " << ans + "1" << '\n';
				cout.flush();
				scanf("%d", &f);
				if(f == 1) ans += "1";
				else {
					cout << "? " << ans + "0" << '\n';
					cout.flush();
					scanf("%d", &f);
					if(f == 1) ans += "0";
					else {
						cout << "? " << "1" + ans << '\n';
						cout.flush();
						scanf("%d", &f);
						if(f == 1) ans = "1" + ans;
						else ans = "0" + ans;
						flag = 1;
					}
				}
			}
			else {
				cout << "? " << "1" + ans << '\n';
				cout.flush();
				scanf("%d", &f);
				if(f == 1) ans = "1" + ans;
				else ans = "0" + ans;
			}
		}
		cout << "! " << ans << '\n';
		cout.flush();
	}
	return 0;
}

D. Minimize the Difference

一个显而易见的结论:最后的答案序列一定是单调不减的。否则可以把前面大的数填补到后面小的数,不会使答案更劣,有可能使答案更优。

因此维护一个平均数单调栈,每次新加入一个数时,若栈顶元素平均数较大,则弹出并合并,直到栈顶元素平均数比当前平均数小,将新段入栈。

最后用最后一个数减去第一个数即为答案。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int T, n;

struct Node {
	long long s, sz;
}stk[N * 2];

int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		int top = 0;
		for(int i = 1; i <= n; i ++) {
			long long x;
			scanf("%lld", &x);
			long long sum = x, size = 1;
			while(top && stk[top].s * size >= sum * stk[top].sz) {
				sum += stk[top].s;
				size += stk[top].sz;
				top --;
			}
			if(sum % size == 0) stk[++ top] = {sum, size};
			else {
				int t = sum % size;
				stk[++ top] = {sum / size * (size - t), size - t};
				stk[++ top] = {(sum / size + 1) * t, t};
			}
		}
		printf("%lld\n", stk[top].s / stk[top].sz - stk[1].s / stk[1].sz);
	}
	return 0;
}

E. Prefix GCD

设 g 为序列的最大公约数,我们将对每个元素进除以 g ,最后,只需将结果乘以 g 即可。

然后,考虑贪心算法。我们将从一个初始为空的数组开始并添加到数组的末尾,使用已有的数组最小化 GCD 的元素。可以观察到,GCD 将在最多 10 次迭代中达到 1 。之后,可以以任何顺序添加剩余的元素。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int T, n, a[N];
bool vis[N];

int gcd(int a, int b) {
	if(!b) return a;
	return gcd(b, a % b);
}

int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		for(int i = 1; i <= n; i ++)
			scanf("%d", &a[i]), vis[i] = false;
		sort(a + 1, a + n + 1);
		long long ans = a[1];
		int t = 0, gd = a[1], cnt = 1;
		while(t != gd) {
			t = gd;
			int id = 0;
			for(int i = 2; i <= n; i ++) {
				if(vis[i]) continue;
				if(gcd(t, a[i]) < gd)
					gd = gcd(t, a[i]), id = i;
			}
			if(id) vis[id] = true;
			ans += gd;
			cnt ++;
		}
		ans += 1ll * gd * (n - cnt);
		printf("%lld\n", ans);
	}
	return 0;
}