【CF VP记录】Codeforces Round 1008 (Div. 2)

比赛链接
本文原文发布于博客园,如您在其他平台刷到此文,请前往博客园获得更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18766146

开题 + 补题情况

坠机场,要是赛时打了的话就又回青了,前两题很快开出来了,第三题脑残了,一开始觉得只需要构造第一个数就行了然后爽吃两发罚时,D 已补出,一个贪心,当时没想明白,E 已补出,很好的一个位运算相关的交互题。
image

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;
}
posted @ 2025-03-11 21:28  天天超方的  阅读(234)  评论(0)    收藏  举报