yLOI2020题解

T1金陵谣:

[yLOI2020] 金陵谣

题目大意:

求有多少 (x, y) 满足 \frac{a}{x} + \frac{b}{c} = \frac{d}{y}

推导这个式子,可得到:

\begin{aligned} \frac{a}{x} + \frac{b}{c} &= \frac{d}{y}\\ acy+bxy &= cdx\\ acy &= cdx - bxy\\ \frac{y}{x} &= \frac{cd - by}{ac}\\ x &= \frac{acy}{cd - by} \end{aligned}

因此,枚举 y ,若 a * c * y % (c * d - b * y) == 0 则存在一组解。最后统计有多少可行解即可。

#include <cstdio>
int main() {
	int T; scanf("%d", &T);
	while (T--) {
		long long a, b, c, d, cnt = 0;
		scanf("%lld %lld %lld %lld", &a, &b, &c, &d);
		for (long long y = 1; b * y < c * d; y++) {
			if (a * c * y % (c * d - b * y) == 0) {
				cnt++;
			}
		}
		printf("%lld\n", cnt);
	}
	return 0;
}

T2:不离

[yLOI2020] 不离

题目大意:

每件衣服都有一个穿戴前的力量值和精神值,以及穿戴后能增加的力量值和精神值。安排一个穿衣顺序,使得每件衣服都穿上,求最小的最初力量值与在初始力量值最小的前提下,初始精神值。

首先很容易可以想到 O(a\times b\times n!) 的做法,即暴力枚举 a, b 以及排列顺序。

接着,优化这个算法:

graph LR
A[a*b*n!] -->|a*b| B[二分答案a与b]
A -->|n!| C[在二分check中实现, 每次把满足a但不满足b的衣服以b为关键字用优先队列维护, 每次更新a之后更新里力量值与优先队列, 更新b之后从优先队列中弹出元素]

因此,复杂度便降到了 O\log(a)\times \log(b) \times n \log n

#include <queue>
#include <cstdio>
#include <algorithm>
#define MAXN 100001
#define MAXLL 1000000000000001
struct node{
	long long a, b, c, d;
}pos[MAXN];
bool cmp_a(node x, node y) {
	return x.a < y.a;
}
struct cmp_b{
	long long pos, b;
	bool operator < (const cmp_b &x) const {
		return x.b < b;
	}
};
long long n;
long long min(long long a, long long b) {return a < b ? a : b;}
long long max(long long a, long long b) {return a > b ? a : b;}
std::priority_queue<cmp_b> q; //按照 b 从小到大的次序
bool check(long long a, long long b) {
	while (!q.empty()) {
		q.pop();
	}
	long long head = 1; //head 存储当前压到第几个a了
	for (int i = 1; i <= n; i++) { //把所有小于a的压进去
		if (pos[i].a > a) break;
		if (pos[i].b > b) { //能满足a但是不能满足b
			q.push((cmp_b){i, pos[i].b});
		}
		if (b >= pos[i].b && a >= pos[i].a) {
			a += pos[i].c, b += pos[i].d;
		}
		head++;
	}
	while (1) {
		bool tag = false;
		while (!q.empty()) {
			if (q.top().b <= b) {
				tag = true;
				a += pos[q.top().pos].c, b += pos[q.top().pos].d;
				q.pop();
			} else {
				break;
			}
		}
		while (pos[head].a <= a && head <= n) {
			tag = true;
			if (pos[head].b > b) { //能满足a但是不能满足b
				q.push((cmp_b){head, pos[head].b});
			}
			if (b >= pos[head].b && a >= pos[head].a) {
				a += pos[head].c, b += pos[head].d;
			}
			head++;
		}
		if (head > n && q.empty()) {
			return true;
		}
		if (!tag && (head <= n || !q.empty())) return false;
	}
	return true;
}
int main() {
	//freopen("forever.in", "r", stdin);
	//freopen("forever.out", "w", stdout);
	int T; scanf("%d", &T);
	long long max_force = 0, max_spirit = 0; scanf("%lld", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld %lld %lld %lld", &pos[i].a, &pos[i].b, &pos[i].c, &pos[i].d);
		max_force = max(max_force, pos[i].a), max_spirit = max(max_spirit, pos[i].b);
	}
	std::sort(pos + 1, pos + 1 + n, cmp_a); //按照 a 从小到大进行排序
	long long la = 0, ra = max_force, ansa = MAXLL, ansb = MAXLL; //二分a
	while (la <= ra) {
		bool tag = false;
		long long a = (la + ra) >> 1, tmp_b = MAXLL, lb = 0, rb = max_spirit;
		while (lb <= rb) { //二分b
			long long b = (lb + rb) >> 1;
			if (check(a, b)) { //a, b能够成为可行解
				tmp_b = min(tmp_b, b), tag = true;
				rb = b - 1;
			} else {
				lb = b + 1;
			}
		}
		if (tag) {
			if (ansa > a) {
				ansa = a, ansb = tmp_b;
			} else if (ansa == a && ansb > tmp_b) {
				ansb = tmp_b;
			}
			ra = a - 1;
		} else {
			la = a + 1;
		}
	}
	printf("%lld %lld\n", ansa, ansb);
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

T3:泸沽寻梦

[yLOI2020] 泸沽寻梦

题目大意:

给定一个数列,进行 m 次操作,每次将 p p+1 位置上的数异或 x ,每此操作结束后求有多少区间异或和为 0 。最后输出所有回答的按位异或之和、有多少次回答的答案是奇数,以及所有答案中的最大值和最小值。

可以求一个异或前缀和,如果区间 [l, r] 的异或和为 0 ,那么可以通过异或的性质得到 pre[l + 1] = pre[r]

因此,可以开一个 unordered_map 来存 pre 中的每个数字出现过多少次,则每个相同前缀和的数字两两都可以组成一个区间,也就是 C_{total[i]}^2= \frac{tong[i] \times (tong[i] - 1)}{2} 。所以复杂度为 O(m)

#include <cstdio>
#include <unordered_map>
#define MAXN 1000001
int num[MAXN];
long long pre[MAXN]; //前缀异或和
std::unordered_map<int, int> total; //存储每个数字出现的次数
std::unordered_map<int, bool> vis; //每个数字是否被访问过
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
int main() {
	//freopen("dream.in", "r", stdin);
	//freopen("dream.out", "w", stdout);
	int n, m; n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		num[i] = read();
		pre[i] = 1ll * num[i] ^ pre[i - 1];
		total[pre[i]]++;
	}
	long long sum = 0;
	total[0]++;
	for (int i = 0; i <= n; i++) {
		if (!vis[pre[i]]) {
			vis[pre[i]] = true;
			if (total[pre[i]] >= 2) sum += 1ll * total[pre[i]] * (total[pre[i]] - 1) / 2; //计算组合数
		}
	}
	long long ans_sum = 0, js = 0, max_ans = 0, min_ans = 9223372036854775807;
	while (m--) {
		int p, x; p = read(), x = read();
		sum = sum - total[pre[p]] + 1;
		total[pre[p]]--;
		pre[p] ^= x;
		sum = sum + total[pre[p]];
		total[pre[p]]++;
		ans_sum ^= sum;
		if (sum % 2 == 1) js++;
		if (sum > max_ans) max_ans = sum;
		if (sum < min_ans) min_ans = sum;
	}
	printf("%lld\n%lld\n%lld\n%lld\n", ans_sum, js, max_ans, min_ans);
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}