Codeforces Round 931 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1934。
大家好啊,因为我非常不会交互和构造,所以后面两题咕了。
以及这场为啥有两道交互两构造妈的,上场就被交互杀了这场又被交互构造双杀了,真好
A
签到。
考虑数形结合,本题等价于在数轴上有 \(n\) 个点,要求选择四个点使给定的式子代表的四段距离之和最大。
先排序,令 \(i=1, j=n, k = 2, l = n-1\) 即可。正确性显然,此时即可取到两点间距离的前四大值。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, a[kN];
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T;
std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
std::sort(a + 1, a + n + 1);
int ans = 0;
for (int i = 1; i <= 2; ++ i) {
for (int j = n; j >= n - 1; -- j) {
ans += a[j] - a[i];
}
}
std::cout << ans << "\n";
}
return 0;
}
B
贪心,数学。
大家好啊,因为我没脑子所以我直接暴力地在最烂复杂度内过掉了这题。
考虑完全背包预处理面值为 \(i(1\le i\le 3000)\) 时所需的最少硬币数量 \(f_{i}\)。一个显然的贪心是尽可能用较大的面值凑,于是考虑枚举该最大面值 \(i(1\le i\le 3000)\),答案即为:
正解同样考虑贪心地尽可能用较大的面值凑,但是对给定数字的性质进行了挖掘:
- 1 不会使用超过 2 次:不如一个 3。
- 3 不会使用超过 1 次:不如一个 6。
- 6 不会使用超过 2 次:不如一个 15 一个 3。
- 10 不会使用超过 2 次:不如两个 15。
不使用 15 且满足上述限制至多能凑出来 \(2 + 3 + 12 + 20 = 37\),除此之外的部分用 15 凑即可。
于是有两种解法,一是直接枚举使用上述四种硬币的次数后用 15 凑,二是预处理 \(n\le 37+15\) 的答案用 15 凑,都比我的恼弹背包明智。
以下是恼弹解法,其实还挺好写的捏。
//贪心,数学
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e3 + 10;
//=============================================================
int c[6] = {0, 1, 3, 6, 10, 15};
int need[kN];
//=============================================================
void Init() {
for (int i = 1; i < kN; ++ i) need[i] = i;
for (int i = 1; i <= 5; ++ i) {
for (int j = c[i]; j < kN; ++ j) {
need[j] = std::min(need[j], need[j - c[i]] + 1);
}
}
}
int Solve(int n_) {
int ret = n_;
for (int i = 1; i < std::min(n_ + 1, kN); ++ i) {
ret = std::min(ret, need[i] * (n_ / i) + need[n_ % i]);
}
return ret;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
Init();
int T; std::cin >> T;
while (T --) {
int n; std::cin >> n;
std::cout << Solve(n) << "\n";
}
return 0;
}
C
交互
妈的交互。
限定四次询问,一个显然的想法是询问矩形的四个顶点,此时每次询问得到的可能有地雷的区域是一条对角线。
若只有一个地雷那简单,询问 \((1, 1)\) 和 \((1, m)\) 得到的对角线的交点即为答案,两次询问即可;若有两个地雷则可能使得第二次询问被另一个地雷影响,使得对角线上没有地雷、对角线无交点、或对角线交点在矩形之外。
不过好在还有两次询问,若对角线有交点则询问交点,若没有交点或者询问后交点上没有地雷,则比较第一次和第二次询问答案哪个小,若第一次小则再询问 \((n, 1)\) 并与第一次询问的对角线求交点,否则询问 \((n, m)\) 并于第二次询问的对角线求交点,可以发现一定有交点且交点处一定有地雷。
求交点设坐标解方程即可。
//交互
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n, m;
//=============================================================
int query(int x_, int y_) {
std::cout << "? " << x_ << " " << y_ << "\n";
fflush(stdout);
int x; std::cin >> x;
return x;
}
std::pair <int, int> Judge1(int d1_, int d2_) {
if ((d1_ - d2_ + m + 1) % 2 == 1) return std::make_pair(-1, -1);
int y = (d1_ - d2_ + m + 1) / 2, x = d1_ - y + 2;
if (y <= 0 || x <= 0) return std::make_pair(-1, -1);
int ret3 = query(x, y);
if (ret3 == 0) return std::make_pair(x, y);
return std::make_pair(0, 0);
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
// std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
int ret1 = query(1, 1), ret2 = query(1, m);
if (ret1 == 0) {
std::cout << "! " << 1 << " " << 1 << "\n";
fflush(stdout);
continue;
}
if (ret2 == 0) {
std::cout << "! " << 1 << " " << m << "\n";
fflush(stdout);
continue;
}
std::pair <int,int> r = Judge1(ret1, ret2);
if (r.first > 0) {
std::cout << "! " << r.first << " " << r.second << "\n";
fflush(stdout);
continue;
}
if (ret1 < ret2) {
int ret4 = query(n, 1);
if (ret4 == 0) {
std::cout << "! " << n << " " << 1 << "\n";
} else {
int x = (ret1 - ret4 + n + 1) / 2, y = ret1 - x + 2;
std::cout << "! " << x << " " << y << "\n";
}
fflush(stdout);
} else {
int ret4 = query(n, m);
if (ret4 == 0) {
std::cout << "! " << n << " " << m << "\n";
} else {
int x = (ret2 - ret4 + n + 1) / 2, y = x - 1 + m - ret2;
std::cout << "! " << x << " " << y << "\n";
}
fflush(stdout);
}
}
return 0;
}
D1
构造,位运算
妈的构造。
记 \(n\) 的最高位和次高位分别为 \(i, j\),\(m\) 的最高位为 \(k\)。
- 当 \(k=i\) 或 \(k=j\) 时直接进行一次操作 \(y=m\) 并取 \(x=y\) 即可。
- 当 \(j<k<i\) 时无解。因为必然会有一次操作令 \(y\) 或者 \(x\oplus y\) 中第 \(i, k\) 位上同时为 1 导致大于 \(x\)。
- 否则有 \(k<j\),先进行操作 \(y = 2^j + (2^{j} - (x - 2^i - 2^j))\) 并取 \(x=x\oplus y\),使 \(x\) 消去第 \(j\) 位上的 1 并使之后全部变为 1,然后进行操作 \(y=m\) 并取 \(x=y\) 即可。
仅需两次操作,太劲爆了!
//构造,位运算
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
LL n, m;
std::vector <LL> ans;
//=============================================================
void Solve() {
std::cin >> n >> m;
LL maxn = 0, maxn2 = 0, maxm = 0;
for (LL i = 0, j = 1; i < 63; ++ i, j <<= 1ll) {
if (n & j) maxn2 = maxn, maxn = j;
if (m & j) maxm = j;
}
if (maxn2 < maxm && maxm < maxn) return ;
if (maxm == maxn || maxm == maxn2) {
ans.push_back(m);
} else {
ans.push_back(n ^ maxn2 | (maxn2 - 1));
ans.push_back(m);
}
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
ans.clear();
Solve();
if (ans.empty()) std::cout << -1 << "\n";
else {
std::cout << ans.size() << "\n" << n << " ";
for (auto x: ans) std::cout << x << " ";
std::cout << "\n";
}
}
return 0;
}
写在最后
学到了什么:
- A:数形结合,差的绝对值转化为数轴上的距离。
- B:数字之间相互转化。
- C:妈的交互
- D:妈的构造