Codeforces Educational Round 117

这篇博客探讨了四种不同类型的算法问题:包括计算两点间的绝对值距离的简单构造,寻找满足特定条件的排列,通过二分法解决信息发送限制问题,以及在数论背景下进行操作序列设计。每种问题都提供了相应的贪心策略、二分查找或辗转相除法的解决方案,并给出了高效的时间复杂度分析。

A. 简单构造

题意:给定二位平面里的两个点的距离 d(p1,p2)=∣x1−x2∣+∣y1−y2∣d(p_1, p_2) = |x_1 - x_2| + |y_1 - y_2|d(p1,p2)=x1x2+y1y2

给定两个点 A,BA, BA,B,其中 A=(0,0),B=(x,y)A = (0, 0), B = (x, y)A=(0,0),B=(x,y),求是否存在一个点 C=(ansx,ansy)C = (ans_x, ans_y)C=(ansx,ansy) 满足:

  • ansx≥0,ansy≥0ans_x \geq 0, ans_y\geq 0ansx0,ansy0
  • d(A,C)=d(A,B)2d(A, C) = \frac{d(A, B)}{2}d(A,C)=2d(A,B)
  • d(B,C)=d(A,B)2d(B, C) = \frac{d(A, B)}{2}d(B,C)=2d(A,B)

d(A,B)+d(B,C)=d(A,B)d(A, B) + d(B, C) = d(A, B)d(A,B)+d(B,C)=d(A,B) 这一条件根据绝对值不等式可知 0≤ansx≤x,0≤ansy≤y0\leq ans_x\leq x, 0\leq ans_y\leq y0ansxx,0ansyy

首先,很显然,如果 d(A,B)=x+yd(A, B) = x + yd(A,B)=x+y 为奇数的话一定不行

然后,我们可以知道 ansx+ansy=x+y2ans_x + ans_y = \frac{x + y}{2}ansx+ansy=2x+y

那么 (0,x+y2)(0, \frac{x + y}{2})(0,2x+y)(x+y2,0)(\frac{x + y}{2}, 0)(2x+y,0) 中一定是有一个满足 0≤ansx≤x,0≤ansy≤y0\leq ans_x\leq x, 0\leq ans_y\leq y0ansxx,0ansyy 的,即为答案

时间复杂度:O(T)O(T)O(T)

#include<bits/stdc++.h>
using namespace std;

int cs, x, y;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> cs;
	while (cs--) {
		cin >> x >> y;
		if ((x + y) & 1) cout << -1 << " " << -1 << '\n';
		else {
			if (x >= (x + y) / 2) cout << (x + y) / 2 << " " << 0 << '\n';
			else cout << 0 << " " << (x + y) / 2 << '\n';
		}
	}
	return 0;
}

B. 贪心

题意:问是否可以构造一个 nnn 的排列(nnn 为偶数),使得前 n2\frac{n}{2}2n 个数的最小值为 aaa,后 n2\frac{n}{2}2n 个数的最大值为 bbb

首先,我们先定第 111 个数为 aaa,第 n2+1\frac{n}{2} + 12n+1 个数为 bbb,那么很显然第 2,3,...,n22, 3, ..., \frac{n}{2}2,3,...,2n 都要大于 aaa,第 n2+1,...,n\frac{n}{2} + 1, ..., n2n+1,...,n 个数都要小于 bbb

那么一个贪心思路是,对于前半段把剩下的数从大向小;对于后半段把剩下的书从小到大填,一个从大向小,一个从小向大,最后填出来一定是不冲突的并且是最优的

最后再判断一下是否符合条件即可

时间复杂度:O(N)O(N)O(N)

#include<bits/stdc++.h>
using namespace std;

int cs, n, a, b;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> cs;
	while (cs--) {
		cin >> n >> a >> b;
		vector<int> now, l, r;
		l.push_back(a); r.push_back(b);
		for (int i = 1; i <= n && (int) r.size() < n / 2; i++) {
			if (i == a || i == b) continue;
			r.push_back(i);
		}
		for (int i = n; i >= 1 && (int) l.size() < n / 2; i--) {
			if (i == a || i == b) continue;
			l.push_back(i);
		}
		int fg = 0;
		for (auto it: l) {
			if (it < a) fg = 1;
		}
		for (auto it: r) {
			if (it > b) fg = 1;
		}
		if (fg) cout << -1 << '\n';
		else {
			for (auto it: l) cout << it << " ";
			for (auto it: r) cout << it << " ";
			cout << '\n';
		}
	}
	return 0;
}

C. 二分

题意:给定 k,nk, nk,n,发送 2k−12k - 12k1 条消息,每条消息依次含有 1,2,...,k−1,k,k−1,...,11, 2, ..., k - 1, k, k - 1, ..., 11,2,...,k1,k,k1,...,1 个表情,但是连续发 nnn 个表情号会被封,一次发一条消息,包含多个表情,问你最多能发多少条消息?

首先我们需要定义一个 f(x)f(x)f(x)

表示前 iii 条消息含有多少个表情,显然有

  • 对于 n≤kn\leq knkf(x)=x(x+1)2f(x) = \frac{x(x + 1)}{2}f(x)=2x(x+1)
  • 对于 n>kn > kn>kf(x)=k(k+1)2+(3k−x−1)(x−k)2f(x) = \frac{k(k + 1)}{2} + \frac{(3k-x-1)(x - k)}{2}f(x)=2k(k+1)+2(3kx1)(xk)

如果 n∈(f(x−1),f(x)]n\in (f(x - 1), f(x)]n(f(x1),f(x)],那么答案就是 xxx,那么怎么找到这个区间呢?

显然二分就可以,因为 f(1),f(2),...,f(2k−1)f(1), f(2), ..., f(2k - 1)f(1),f(2),...,f(2k1) 是连续的且是递增的

时间复杂度:O(log⁡K)O(\log K)O(logK)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll

int cs, k, x;

int cal(int n) {
	if (n <= k) return (1 + n) * n / 2;
	int ret = (k + 1) * k / 2;
	ret += (k - 1 + 2 * k - n) * (n - k) / 2;
	return ret;
}

signed main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> cs;
	while (cs--) {
		cin >> k >> x;
		int l, r, ans;
		l = 1, r = 2 * k - 1, ans = 2 * k - 1;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (cal(mid) >= x) {
				ans = mid;
				r = mid - 1;
			}
			else {
				l = mid + 1;
			}
		}
		cout << ans << '\n';

	}
	return 0;
}

D. 简单数论

题意:给定 a,ba, ba,b,可以做以下两个操作

  • a:=∣a−b∣a:= |a - b|a:=ab
  • b:=∣a−b∣b:=|a - b|b:=ab

问是否存在某种操作序列,是的 a,ba, ba,b 中的至少一个数为 xxx

这是一个类似与辗转相除法求 gcd⁡\gcdgcd 的问题

对于两个数 a,ba, ba,b,假设现在满足 a>ba > ba>b,可以发现无论怎么操作一定会出现 a−b,ba - b, bab,b 这一最近的状态

我们可以类似与辗转相除法进行,将 aaa 不断 −b-bb 到不能减为止,将 a,ba, ba,b 交换,继续上述步骤,并判断其中是否有 xxx 出现,这样能取遍所有的数所以可行

时间复杂度:O(log⁡A)O(\log A)O(logA)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll

int cs, a, b, x;

void solve(void) {
	int g = __gcd(a, b);
	if (x % g) {
		cout << "NO" << '\n';
		return;
	}
	a /= g; b /= g; x /= g;
	while (a && b) {
		if (a < b) swap(a, b);
		if (a % b == x % b && a >= x) {
			cout << "YES\n";
			return;
		}
		a %= b;
	}
	cout << "NO\n";
}

signed main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> cs;
	while (cs--) {
		cin >> a >> b >> x;
		solve();
	}
	return 0;
}

E. 贪心

题意:有 nnn 个学生,每个学生会从墙上随机选 KKK 个信息读(如果墙上信息小于 KKK 则全读),老师希望第 iii 个学生选择标号为 mim_imi 的读

问怎么想墙上放信息,使得读到老师希望的信息的学生个数的期望最大

首先考虑这个问题:假设向墙上放 ttt 个标签,如何选择使得其期望最大?

假设希望当前学生读到的信息是 mim_imi,这个学生取 kik_iki 个,墙上的 ttt 个标签含有 mim_imi,那么他取到 mim_imi 的概率为:

  • 如果 ki≤tk_i \leq tkit ,概率为 (t−1ki−1)(tki)=kit\frac{{{t - 1}\choose {k_i - 1}}}{{{t}\choose {k_i}}} = \frac{k_i}{t}(kit)(ki1t1)=tki
  • 如果 ki>tk_i > tki>t,概率为 111

综合上面两个式子,概率可以表示为 min⁡(ki,t)t\frac{\min(k_i, t)}{t}tmin(ki,t)

对于每个信息来说,我们计算其 ∑mimin⁡(ki,t)t\sum\limits_{m_i}\frac{\min(k_i, t)}{t}mitmin(ki,t) 就表示当前信息的贡献

我们去贡献最大的 ttt 个信息就是答案

我们不难观察到,当 t>kit > k_it>ki 的时候,min⁡(ki,t)t=kit\frac{\min(k_i, t)}{t} = \frac{k_i}{t}tmin(ki,t)=tki ,随着 ttt 的增加这个值是变小的

因为 1≤ki≤201\leq k_i\leq 201ki20 这一条件,当 t>20t > 20t>20 时,随着 ttt 的增加,所有信息类的贡献都会不断减少,相对 t≤20t\leq 20t20 一定不是最优的,所以我们只需要考虑 t≤20t\leq 20t20 的情况即可

时间复杂度:O(Nmax⁡(Ki))O(N\max(K_i))O(Nmax(Ki))

#include<bits/stdc++.h>
using namespace std;


map<int, vector<int>> mp;
vector<int> tmp;
int n;

bool cmp(pair<int, int> a, pair<int, int> b) {
	if (a.first == b.first) return a.second > b.second;
	return a.first > b.first;
}

pair<double, vector<int>> cal(int len) {
	vector<pair<int, int>> v;
	for (auto& it: mp) {
		vector<int>& now = it.second;
		int val = 0;
		for (auto q: now) {
			val += min(q, len);
		}
		v.push_back({val, it.first});
	}
	sort(v.begin(), v.end(), cmp);
	tmp.clear();
	int sum = 0;
	for (int i = 0; i < len && i < (int) v.size(); i++) {
		sum += v[i].first;
		tmp.push_back(v[i].second);
	}
	return make_pair(sum * 1.0 / len, tmp);
}

vector<int> tmpv, ansv;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0); cout.precision(10);
	cin >> n;
	for (int i = 1, m, k; i <= n; i++) {
		cin >> m >> k;
		mp[m].push_back(k);
		tmpv.push_back(k);
	}

	double ans = 0;
	for (int i = 1; i <= 20; i++) {
		pair<double, vector<int> > vv = cal(i);
		if (vv.first > ans) {
			ans = vv.first;
			ansv = vv.second;
		}
	}
	cout << ansv.size() << endl;
	for (auto it: ansv) {
		cout << it << " ";
	}
	cout << endl;
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值