A. 简单构造
题意:给定二位平面里的两个点的距离 d(p1,p2)=∣x1−x2∣+∣y1−y2∣d(p_1, p_2) = |x_1 - x_2| + |y_1 - y_2|d(p1,p2)=∣x1−x2∣+∣y1−y2∣
给定两个点 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 0ansx≥0,ansy≥0
- 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 y0≤ansx≤x,0≤ansy≤y
首先,很显然,如果 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 y0≤ansx≤x,0≤ansy≤y 的,即为答案
时间复杂度: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 - 12k−1 条消息,每条消息依次含有 1,2,...,k−1,k,k−1,...,11, 2, ..., k - 1, k, k - 1, ..., 11,2,...,k−1,k,k−1,...,1 个表情,但是连续发 nnn 个表情号会被封,一次发一条消息,包含多个表情,问你最多能发多少条消息?
首先我们需要定义一个 f(x)f(x)f(x)
表示前 iii 条消息含有多少个表情,显然有
- 对于 n≤kn\leq kn≤k,f(x)=x(x+1)2f(x) = \frac{x(x + 1)}{2}f(x)=2x(x+1)
- 对于 n>kn > kn>k,f(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(3k−x−1)(x−k)
如果 n∈(f(x−1),f(x)]n\in (f(x - 1), f(x)]n∈(f(x−1),f(x)],那么答案就是 xxx,那么怎么找到这个区间呢?
显然二分就可以,因为 f(1),f(2),...,f(2k−1)f(1), f(2), ..., f(2k - 1)f(1),f(2),...,f(2k−1) 是连续的且是递增的
时间复杂度:O(logK)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:=∣a−b∣
- b:=∣a−b∣b:=|a - b|b:=∣a−b∣
问是否存在某种操作序列,是的 a,ba, ba,b 中的至少一个数为 xxx
这是一个类似与辗转相除法求 gcd\gcdgcd 的问题
对于两个数 a,ba, ba,b,假设现在满足 a>ba > ba>b,可以发现无论怎么操作一定会出现 a−b,ba - b, ba−b,b 这一最近的状态
我们可以类似与辗转相除法进行,将 aaa 不断 −b-b−b 到不能减为止,将 a,ba, ba,b 交换,继续上述步骤,并判断其中是否有 xxx 出现,这样能取遍所有的数所以可行
时间复杂度:O(logA)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 tki≤t ,概率为 (t−1ki−1)(tki)=kit\frac{{{t - 1}\choose {k_i - 1}}}{{{t}\choose {k_i}}} = \frac{k_i}{t}(kit)(ki−1t−1)=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}mi∑tmin(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 201≤ki≤20 这一条件,当 t>20t > 20t>20 时,随着 ttt 的增加,所有信息类的贡献都会不断减少,相对 t≤20t\leq 20t≤20 一定不是最优的,所以我们只需要考虑 t≤20t\leq 20t≤20 的情况即可
时间复杂度: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;
}
这篇博客探讨了四种不同类型的算法问题:包括计算两点间的绝对值距离的简单构造,寻找满足特定条件的排列,通过二分法解决信息发送限制问题,以及在数论背景下进行操作序列设计。每种问题都提供了相应的贪心策略、二分查找或辗转相除法的解决方案,并给出了高效的时间复杂度分析。
1276

被折叠的 条评论
为什么被折叠?



