【CF VP记录】Codeforces Round 1008 (Div. 2)
比赛链接
本文原文发布于博客园,如您在其他平台刷到此文,请前往博客园获得更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18766146
开题 + 补题情况
坠机场,要是赛时打了的话就又回青了,前两题很快开出来了,第三题脑残了,一开始觉得只需要构造第一个数就行了然后爽吃两发罚时,D 已补出,一个贪心,当时没想明白,E 已补出,很好的一个位运算相关的交互题。

A. Final Verdict
瞎猜的,只要所有数的和除以 \(n\) 得到的值为 \(x\) 一定有解,暂时没想到如何证明,有空再来证一证。
点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long
const int N = 2e5 + 9;
void solve()
{
int n, x;std::cin >> n >> x;
std::vector<int> a(n);
int sum = 0;
for(auto &i : a) {
std::cin >> i;
sum += i;
}
if(sum % n == 0 && sum / n == x) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
B. Vicious Labyrinth
题目要让所有人离 \(n\) 的距离最小化。
我们对 \(k\) 分奇偶讨论:
- 如果 \(k\) 为奇数,那么我们只需要把 \(n\) 传送到 \(n - 1\),其余位置传送到 \(n\),那么经过一次传送后,就只有一个人在 \(n - 1\) 的位置,其他人均在 \(n\) 的位置,接下来偶数次只会在这两个位置反复横跳,答案为 \(1\)。
- 如果 \(k\) 为偶数,那么我们把 \(n - 1\) 传送到 \(n\),其余位置传送到 \(n - 1\),这样再来一次传送后,就只有一个人在 \(n - 1\) 的位置,其他人均在 \(n\) 的位置,接下来偶数次只会在这两个位置反复横跳,答案为 \(1\)。
由于不能往原位置传送,所以至少有一个人无法抵达 \(n\),因此答案至少为 \(1\),所以上述构造为最优解。
点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long
const int N = 2e5 + 9;
void solve()
{
int n, k;std::cin >> n >> k;
if(k & 1) {
for(int i = 1;i <= n;i ++) {
if(i == n)std::cout << n - 1 << ' ';
else std::cout << n << ' ';
}
} else {
for(int i = 1;i <= n;i ++) {
if(i == n - 1)std::cout << n << ' ';
else std::cout << n - 1 << ' ';
}
}
std::cout << '\n';
}
C. Breach of Faith
一开始以为只要把第一项当成未知项,然后把后面的数排一下序求一下就行了,直到我搓出了这个样例 \(2, 3, 4, 5\),这个样例按这个想法来的话,求出来的值是 \(-2\),显然不符合题意,并且除了这种情况,还有可能导致数字重复,同样不符合题意。
对于此题,我们对题目中的式子进行变形:\(0 = -a_1 + a_2 -a_3 + ... +a_{2 \times n} - a_{2 \times n + 1}\)。
我们首先对所给 \(b\) 数组进行一下从小到大排序,因为这样可以一减一加后是正数,更容易命中答案(其实这个也是猜的,为什么要排序具体的也没细证)。
然后,我们对上面那个式子枚举每一项作为消失项,通过对上面的新式子进行移项求出这一项的值,然后判断一下这个值是否合法,如果合法,这就是满足题意的构造。
对于移项后其他项的和,可以通过记录奇偶前缀和来快速求出。
时间复杂度:\(O(n \log n)\),\(\log n\) 来源于我使用了 map 记录一个数字是否出现过。
点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long
const int N = 2e5 + 9;
void solve()
{
int n;std::cin >> n;
std::vector<int> a(2 * n + 2), b(2 * n + 1);
std::map<int, bool> vis;
for(int i = 1;i <= 2 * n;i ++) {
std::cin >> b[i];
vis[b[i]] = true;
}
sort(b.begin() + 1, b.end());
std::vector<int> preodd(2 * n + 2, 0), preeve(2 * n + 2, 0);
for(int i = 1;i <= 2 * n;i ++) {
if(i & 1) {
preodd[i] = preodd[i - 1] + b[i];
preeve[i] = preeve[i - 1];
}
else {
preodd[i] = preodd[i - 1];
preeve[i] = preeve[i - 1] + b[i];
}
}
for(int i = 1;i <= 2 * n + 1;i ++) {
int ans = 0;
ans += preodd[i - 1];
ans -= preeve[i - 1];
ans += preeve[2 * n] - preeve[i - 1];
ans -= preodd[2 * n] - preodd[i - 1];
if(i & 1) {
ans = -ans;
}
if(!vis.count(ans) && ans > 0) {
for(int j = 1;j < i;j ++) {
a[j] = b[j];
}
a[i] = ans;
for(int j = i;j <= 2 * n;j ++) {
a[j + 1] = b[j];
}
break;
}
}
for(int i = 1;i <= 2 * n + 1;i ++) {
std::cout << a[i] << " ";
}
std::cout << '\n';
}
D. Scammy Game Ad(补题)
fun fact:我确实刷到过这个游戏广告。
这个题的正解是贪心,先上结论:对于每次多出来的的人,我们只需要根据下一个位置的两扇门的情况来决定 all in 给哪一边。
- 如果下一个位置两扇门一扇是加法一扇是乘法,给乘法那一边。
- 如果下一个位置两扇门都是加法,那么按再后两扇门的情况进行分配。
- 如果下一个位置两扇门都是乘法,那么继续分类讨论:
- 如果两个数字不同,哪个大给哪边。
- 如果两个数字相同,按再后两扇门的情况进行分配。
结论一出,可以看出,这是一个典型的贪心思想,那就是保证当前决策得到的结果尽可能大,那么最后结果也会尽可能大,那么这么想的合理性在哪里呢?
在于一句很重要的话:多出来的人可以随意分配。
前面已经确定的人,我们无法改变了,后面的运算式,我们也无法改变了。
记当前得到的新人数为 \(x\),我们要让结果尽可能大,我们需要做的是,让 \(x\) 尽可能做乘法,因为加法是无法改变人数的,加法是多少就是多少,但乘法是和当前基数有关的,假设乘的是 \(y\),则经过乘法后得到的是 \(x \times y\),不难想到,如果要让结果尽可能大,那就是 \(x\) 尽可能大,\(y\) 也要尽可能大,也就是说,我们要让当前 \(x\) 乘上一个更大的数,而当前的 \(x\) 又是由上一步得到的,我们要让当前的 \(x\) 尽可能大,也就是让上一步的 \(x \times y\) 尽可能大,从后往前按这个逻辑推下去的话,也就可以得出这个结论:我们要让最后结果尽可能大,也就是让每一步的结果要尽可能大,因此上述贪心结论成立。
代码很短,搞一个后缀求一下当前的决策,然后从前往后求解即可。
点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long
const int N = 2e5 + 9;
int n;
struct door {
char l, r;
int s, e;
};
int change(int x, char c, int y) {
if(c == 'x') {
x *= y;
} else {
x += y;
}
return x;
}
void solve()
{
std::cin >> n;
std::vector<door> a(n);
for(int i = 0;i < n;i ++) {
std::cin >> a[i].l >> a[i].s >> a[i].r >> a[i].e;
}
std::vector<int> suf(n + 1);
suf[n] = 2;
for(int i = n - 1;i >= 0;i --) {
if(a[i].l == 'x' && a[i].r == 'x') {
if(a[i].s > a[i].e) {
suf[i] = 0;
} else if(a[i].s < a[i].e) {
suf[i] = 1;
} else {
suf[i] = suf[i + 1];
}
} else if(a[i].l == 'x' && a[i].r != 'x') {
suf[i] = 0;
} else if(a[i].l != 'x' && a[i].r == 'x') {
suf[i] = 1;
} else {
suf[i] = suf[i + 1];
}
}
int s = 1, e = 1;
int ans = 0;
for(int i = 0;i < n;i ++) {
int s1 = change(s, a[i].l, a[i].s);
int e1 = change(e, a[i].r, a[i].e);
ans = s1 + e1;
if(i == n)break;
int sum = s1 + e1 - s - e;
if(suf[i + 1] == 0) {
s += sum;
} else if(suf[i + 1] == 1) {
e += sum;
} else {
s += sum;
}
}
std::cout << ans << '\n';
}
E. Finding OR Sum(补题)
又是一个因为赛时不换题没有开出来的题,及时换题真的很重要啊!
关于此题,我们首先对 \(n | x\) 变一下形,\(n | x = n + (x \& \sim n)\),也就是把 \(n\) 和 \(x\) 同时为 \(1\) 的位在 \(x\) 中删掉,这样的话,为 \(1\) 的位要么在 \(n\),要么在 \(x\),因此我们可以得出 \((x \& \sim n) + (y \& \sim n) = (n | x) + (n | y) - 2 \times n\)。
我们要得到 \((m | x) + (m | y)\) 的值,只需要把每一位上 \(x\) 和 \(y\) 的 \(1\) 的出现数量情况找出来,再根据 \(m\) 的每一位是 \(1\) 还是 \(0\) 来模拟或运算以及位运算即可。
那么我们如何在两次询问的情况下,把每一位的 \(1\) 的出现数量找出来呢?
我们注意到,在二进制位的情况下相加,当前位为第 \(i\) 位,如果第 \(i + 1\) 位和 \(i - 1\) 位都是 \(0\),则两个数的第 \(i\) 位相加只会有这两种情况:
- 两个 \(1\):第 \(i + 1\) 位为 \(1\),第 \(i\) 位为 \(0\)。
- 两个 \(0\):第 \(i + 1\) 位和 第 \(i\) 位均为 \(0\)。
- 一个 \(1\) 一个 \(0\):第 \(i\) 位为 \(1\),第 \(i + 1\) 位为 \(0\)。
那么,我们就可以根据上面那个式子,求一次奇数位全部变成 \(0\) 的两个数的和,把偶数位的 \(1\) 的出现次数情况求出来,求一次偶数位全部变成 \(0\) 的两个数的和,把奇数位的 \(1\) 的出现次数情况求出来。
然后,根据下面的规则逐位求解:
- 如果 \(m\) 第 \(i\) 位为 \(1\),那么这一位对答案的贡献就是 \(1 \ll (i + 1)\)。
- 如果 \(m\) 第 \(i\) 位为 \(0\),那么这一位对答案的贡献就看 \(1\) 的出现次数,如果出现次数为 \(2\),那就是 \(1 \ll (i + 1)\),如果出现次数为 \(1\),那就是 \(1 \ll i\)。
点击查看代码
#include <bits/stdc++.h>
#define inf 2e18
#define int long long
const int N = 2e5 + 9;
int ask(int x) {
std::cout << x << std::endl;
int op;std::cin >> op;
return op;
}
void solve()
{
std::vector<int> a(30);
int tmp = 0;
for(int i = 0;i < 30;i += 2) {
tmp |= (1ll << i);
}
int res1 = ask(tmp) - tmp * 2;
for(int i = 1;i < 30;i += 2) {
if(res1 & (1ll << (i + 1))) {
a[i] = 2;
} else if(res1 & (1ll << i)) {
a[i] = 1;
} else {
a[i] = 0;
}
}
tmp = 0;
for(int i = 1;i < 30;i += 2) {
tmp |= (1ll << i);
}
int res2 = ask(tmp) - tmp * 2;
for(int i = 0;i < 30;i += 2) {
if(res2 & (1ll << (i + 1))) {
a[i] = 2;
} else if(res2 & (1ll << i)) {
a[i] = 1;
} else {
a[i] = 0;
}
}
std::cout << '!' << std::endl;
int ck;std::cin >> ck;
int ans = 0;
for(int i = 0;i < 30;i ++) {
if(ck & (1 << i)) {
ans += (1ll << (i + 1));
} else if(a[i] == 2) {
ans += (1ll << (i + 1));
} else if(a[i] == 1) {
ans += (1ll << i);
}
}
std::cout << ans << std::endl;
}
作者: 天天超方的
出处: https://www.cnblogs.com/TianTianChaoFangDe
关于作者:ACMer,算法竞赛爱好者
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显醒目位置给出, 原文链接 如有问题, 可邮件(1005333612@qq.com)咨询.

浙公网安备 33010602011771号