一万五参赛,赛时(VP)排名 80

A. Dora's Set

比较新的结论题。显然,一个合法三元组最多只能有一个偶数。每个偶数都可以跟相邻两个奇数组成合法三元组。

因此,答案为奇数个数的二分之一(下取整)。

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

int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d%d", &l, &r);
		int num2 = r / 2 - l / 2;
		if(l % 2 == 0) num2 ++;
		printf("%d\n", (r - l + 1 - num2) / 2);
	}
	return 0;
}

B. Index and Maximum Value

简单结论题。显然,每次修改不会改变最大值的地位,要么最大值跟着变小或者变大,要么最大值没有影响。

因此每次只需要判断最大值是否在变化区间内即可。

VP时看错题了,白白耽误了 15 min

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

int main() {
	scanf("%d", &T);
	while(T --) {
		int mx = 0;
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; i ++)
			scanf("%d", &a[i]), mx = max(mx, a[i]);
		for(int i = 1; i <= m; i ++){
			char op[2]; int l, r;
			scanf("%s%d%d", op, &l, &r);
			if(op[0] == '+' && r >= mx && l <= mx) mx ++;
			if(op[0] == '-' && r >= mx && l <= mx) mx --;
			printf("%d ", mx);
		}
		puts("");
	}
	return 0;
}

C. Dora and C++

裴蜀定理。显然,根据裴蜀定理,每个数都可以加减任意次 gcd(a,b) ,且这是最小最优的数。

因此,对每个数取模,框定在 gcd(a,b) 范围内,并且从小到大依此加一次 gcd(a,b) ,动态更新答案即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int T, n, a, b, c[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%d%d", &n, &a, &b);
		int gd = gcd(a, b);
		for(int i = 1; i <= n; i ++) {
			scanf("%d", &c[i]);
			c[i] %= gd;
		}
		sort(c + 1, c + n + 1);
		int ans = c[n] - c[1];
		for(int i = 2; i <= n; i ++)
			ans = min(ans, c[i - 1] - c[i] + gd);
		printf("%d\n", ans);
	}
	return 0;
}

D. Iris and Game on the Tree

怎么还是结论题。。。

手玩一下可以发现,一个叶子节点代表的 01 串的权值只能为 0 或者 1 ,且只跟首尾字符是否一致有关,与中间无关。

分类讨论。

若根节点可以填,优先填在叶子出现最多的字符,若一样多,先填中间无关紧要的(此时后手填根节点更优)。

若根节点固定,则抢占叶子节点的有利字符即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int T, n, w[N], cnt[4];
vector<int> g[N];
char s[N];

void dfs(int x, int fa) {
	bool is_leaf = true;
	for(auto y: g[x]) {
		if(y == fa) continue;
		dfs(y, x);
		is_leaf = false;
	}
	if(is_leaf) cnt[w[x]] ++;
	else if(x != 1 && w[x] == 2) cnt[3] ++;
}

int main() {
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		for(int i = 1; i <= n; i ++) g[i].clear();
		cnt[0] = cnt[1] = cnt[2] = cnt[3] = 0; 
		for(int i = 2; i <= n; i ++) {
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		scanf("%s", s + 1);
		for(int i = 1; i <= n; i ++) {
			if(s[i] == '0') w[i] = 0;
			if(s[i] == '1') w[i] = 1;
			if(s[i] == '?') w[i] = 2;
		}
		dfs(1, 0);
		if(w[1] != 2) printf("%d\n", cnt[w[1] ^ 1] + (cnt[2] + 1) / 2);
		else {
			if(cnt[0] != cnt[1])
				printf("%d\n", max(cnt[1], cnt[0]) + cnt[2] / 2);
			else
				printf("%d\n", cnt[0] + ((cnt[3] & 1) ? (cnt[2] + 1) / 2 : cnt[2] / 2));
		}
	}
	return 0;
}

E. Iris and the Tree

没错还是结论题。。。

显然,若 dis(i,i+1) 中间有无权边,可以将这条无权边的边权设置成最大,因此我们只需要知道含无权边的路径有多少即可。

又显然,每条边会被经过两次,对于 i 节点对应边,分别是 dis(i-1,i) 和 dis(mx,mx+1) ,mx 是 i 节点所在子树的最大节点。

因此每次加一条边权,两条对应路径的无权边减一。计算相应答案即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll T, n, w, cnt, cur;
ll mx[N], dis[N], p[N];
vector<int> g[N];

void dfs(int x) {
	if(x == cur + 1) {
		dis[cur] = cnt;
		cnt = 0; cur ++;
	}
	mx[x] = x;
	for(auto y: g[x]) {
		cnt ++;
		dfs(y);
		mx[x] = max(mx[x], mx[y]);
	}
	if(x != 1) cnt ++;
}

int main() {
	scanf("%lld", &T);
	while(T --) {
		scanf("%lld%lld", &n, &w);
		for(int i = 1; i <= n; i ++)
			g[i].clear();
		for(int i = 2; i <= n; i ++) {
			scanf("%d", &p[i]);
			g[p[i]].push_back(i);
		}
		cur = 1; cnt = 0;
		dfs(1);
		dis[cur] = cnt;
		cnt = n;
		ll sum = 0;
		for(int i = 2; i <= n; i ++) {
			ll x, y;
			scanf("%lld%lld", &x, &y);
			if(-- dis[x - 1] == 0) cnt --;
			if(-- dis[mx[x]] == 0) cnt --;
			sum += y;
			printf("%lld ", sum * 2 + cnt * (w - sum));
		}
		puts("");
	}
	return 0;
}