【暑期多校补题记录】2025牛客多校(3)(4) 杭电多校(2)(3)

本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/19007314

开题情况

7.22牛客多校3 : 5题 - ADEFJ
7.24牛客多校4 : 1题 - F
7.21杭电多校2 : 4题 - 2、6、8、9
7.25杭电多校3 : 4题 - 2、7、8、12

暑期多校第二周,这周打得有点不尽如人意,主要是好几场的mid题就开始卡了,尤其是牛客多校4,被自己一个很低级的错误葬送了B题,还需多练!

本人个人补题情况

7.22牛客多校3 : 3题 - BEH
7.24牛客多校4 : 2题 - BG
7.21杭电多校2 : 3题 - 7、9、12
7.25杭电多校3 : 2题 - 4、9

有些题场上虽然过了,但一看并非最优解,就也纳入补题阵列了。

牛客多校3补题

B - Bitwise Puzzle

赛时试图枚举所有初始状态来求解,但还是一WA再WA。
首先 \(b\) 可以进行按位右移操作,所以 \(b\) 是可以变成 \(0\) 的,那么,我们就有一个大致的方向了:用 \(b\) 来改 \(a\),把 \(a\) 改成 \(c\),然后把 \(b\) 异或上 \(a\)
\(a\)\(b\) 不都为 \(0\) 的时候,如果 \(a\)\(b\) 的最高位不同,我们一定可以通过一次 \(a \oplus b\),来把 \(a\)\(b\) 的最高位变成一样的(因为最高位不等的话,在该位,一定有一个 \(1\) 一个 \(0\),异或一下就可以把 \(0\) 改成 \(1\)),记这个最高位为 \(k\)
由于我们要把 \(b\) 变成 \(0\),所以 \(b\) 的最高位 \(1\) 会逐步右移,就足以修改掉 \(a\)\(\leq k\) 的位了,因此对于 \(a\)\(\leq k\) 的位,只需要通过把 \(b\) 逐步右移,对于 \(b\) 当前的最高位,比对 \(a\)\(c\) 在该位是否相等,如果不相等,就用一次异或操作进行修改。由于 \(b\) 的最高位往左全都是 \(0\),所以修改不会对 \(a\) 已修改的位造成影响。
那如果 \(c\) 的最高位 \(> k\) 呢?\(b\) 右移逐步修改是修改不到更高的位的,那么我们可以换个角度,既然 \(b\) 不能左移,那就让 \(a\) 左移,我们用第 \(k\) 位去修改 \(a\) 的第 \(k\) 位,然后通过左移操作把修改的位往左运输过去,不就行了吗。
因此,我们的操作顺序应该是:

  • 如果 \(a\)\(b\) 最高位不等,那么用一次 \(a \ oplus b\) 把最高位对齐。
  • 通过 \(a \oplus b\)\(a << 1\)\(> k\) 的位修改得和 \(c\) 一样。
  • 通过 \(a \oplus b\)\(b >> 1\)\(\leq k\) 的位修改得和 \(c\) 一样。
  • \(a \oplus b\)\(b\) 改成 \(c\)

计算一下操作次数:第一步最多执行 \(1\) 次操作,第二步和第三步每次修改最多有 \(2\) 个操作参与,总的位数最多为 \(31\) 位,所以最多 \(31 * 2 = 62\) 次,最后一步执行 \(1\) 次操作,加起来刚好 \(64\) 次,非常稳定!
再看一下上面这个做法成立的条件,我们需要把 \(a\)\(b\) 的最高位对齐,因此,至少要有一个非 \(0\),如果两个都是 \(0\),则没有任何的修改机会,当且仅当 \(c = 0\) 的时候有解,否则无解。

点击查看代码
/* by 01022.hk - online tools website : 01022.hk/zh/calcvolume.html */
#include <bits/stdc++.h>
#define int long long

using i64 = long long;

void solve() {
    int a, b, c;std::cin >> a >> b >> c;

    if(a == b && b == 0) {
        if(c == 0) {
            std::cout << 0 << "\n\n";
        } else {
            std::cout << -1 << '\n';
        }
        return;
    }

    std::vector<int> op;

    if(std::__lg(a) != std::__lg(b)) {
        if(std::__lg(a) < std::__lg(b)) {
            a ^= b;
            op.push_back(3);
        } else {
            b ^= a;
            op.push_back(4);
        }
    }

    int ch = std::__lg(c) - std::__lg(a);
    int now = std::__lg(a);
    if(ch > 0) {
        for(int i = 0;std::__lg(a) < std::__lg(c);i ++) {
            if((c >> (std::__lg(c) - i) & 1) != (a >> now & 1)) {
                a ^= b;
                op.push_back(3);
            }
            a <<= 1;
            op.push_back(1);
        }
    }

    for(int i = now;i >= 0;i --) {
        if((c >> i & 1) != (a >> i & 1)) {
            a ^= b;
            op.push_back(3);
        }
        b >>= 1;
        op.push_back(2);
    }

    while(b) {
        b >>= 1;
        op.push_back(2);
    }

    b ^= a;
    op.push_back(4);

    std::cout << op.size() << '\n';
    for(auto &x : op) {
        std::cout << x << ' ';
    }

    std::cout << '\n';
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    
    int t = 1;
    std::cin >> t;
    
    while (t--) {
        solve();
    }
    
    return 0;
}

E - Equal

这个题赛时队友想的。
首先,如果 \(n\) 为奇数,那么一定可行,因为对除了某个数字外的其它的所有数字(一定是偶数个)都做一个相同的操作,等于对这个数字做一个单点的反向操作,那么一定可以把所有数字都变成一样的。(类似于上半年南昌邀请赛的一个题)
如果 \(n\)\(2\),特殊判断一下,当且仅当两个数字相同的时候才可行,因为此时只能对这两个数字操作,且操作都相同。
如果 \(n\) 为其它偶数,那么,只需要判断,各个质数在这些所有数中的出现次数,是否为偶数次,如果为偶数次,则一定可行,因为每次操作对质数次数变化都是偶数次,如果一开始次数为奇数次,那么最后必然会有孤立的数,并且不能像奇数的情况那样,通过修改其它的来看作反向修改自己,因为此时除了孤立出来的那个数,剩下的数的总数是偶数个。

处理方法,赛时选择对每个数进行唯一分解来计数,通过素数剪枝卡过去了,此做法很极限,瓶颈在于唯一分解。
那么再观察一下这个题要我们干嘛,质数出现次数为偶数,那什么方法很适合用来统计信息出现的奇偶性呢?
那不就是异或哈希嘛!
我们通过给每个质数随机一个哈希值,然后在素筛的过程中通过异或处理出每个数的哈希值,由于次数为偶数的时候,异或值为 \(0\),因此,判断数组中所有分解出来的质数的出现次数是否都为偶数,也就是判断数组所有数的哈希值异或起来的值是否为 \(0\)

时间复杂度:\(O(nlog(logn))\)

点击查看代码
/* by 01022.hk - online tools website : 01022.hk/zh/calcvolume.html */
#include <bits/stdc++.h>
#define int long long

const int N = 5e6 + 9;
bool notprime[N];
std::vector<int> prime;
bool vis[N];
int hash[N];

std::mt19937_64 rnd(std::chrono::steady_clock::now().time_since_epoch().count());

int ran(int l, int r) 
{
    return rnd() % (r - l + 1) + l;
}

void init() {
    notprime[0] = notprime[1] = true;

    for(int i = 2;i < N;i ++) {
        if(!notprime[i])prime.push_back(i);

        for(int j = 0;j < prime.size() && prime[j] * i < N;j ++) {
            notprime[prime[j] * i] = true;
            if(i % prime[j] == 0) {
                break;
            }
        }
    }

    for(int i = 0;i < prime.size();i ++) {
        hash[prime[i]] = ran(1e9, 1e18);
        for(int j = prime[i] * 2;j < N;j += prime[i]) {
            int tmp = j;
            while(tmp % prime[i] == 0) {
                tmp /= prime[i];
                hash[j] ^= hash[prime[i]];
            }
        }
    }
}

void solve() {
    int n;std::cin >> n;

    std::vector<int> a(n);

    for(auto &x : a) {
        std::cin >> x;
    }

    if(n & 1) {
        std::cout << "YES\n";
        return;
    }

    if(n == 2) {
        if(a[0] == a[1]) {
            std::cout << "YES\n";
        } else {
            std::cout << "NO\n";
        }
        return;
    }

    int ans = 0;
    for(auto &x : a) {
        ans ^= hash[x];
    }

    std::cout << ((ans == 0) ? "YES\n" : "NO\n");
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    init();

    int t = 1;std::cin >> t;
    while(t --) {
        solve();
    }

    return 0;
}

H - Head out to the Target

赛时磕 B 去了,没看这题,现在回想起来应该和队友一起磕这题的。

这个题首先要明确一点,如果我们假设一个结点 \(x\) 是最后答案所在点,那么必定是从根沿着路径走下来,不会走到别的地方去(也就是把别的路切了),不然一定不优。
那么其实我们需要关注的,就是到某个时间点的时候,目标到根的路径上的所有结点,也就是目标的所有祖先结点,是否都有可能在最优情况下走过。
当一个目标出现时,只需要把它的最近的祖先结点往下扩展一格就行了,一个祖先结点是可能走过的,那么这个目标出现时,完全可以认为当前棋子就在这个祖先结点上,因为在其它结点的话,勾引过来到的位置,也一定是之前已经到过的,所以每次最多扩展一格。
只需要判断目标在每个结点时,该结点是否有可能走过即可(因为也可以切断它下面的边避免它被勾引走)。
这个祖先结点可以使用倍增进行查找,单次时间复杂度为 \(O(logn)\),由于每个点最多被扩展一次,所以最多扩展 \(n\) 次,然后就一定有解了。
时间复杂度:\(O(nlogn)\)

点击查看代码
#include <bits/stdc++.h>
#define int long long

const int N = 1e7 + 9;

void returm() {
    int n, k;std::cin >> n >> k;

    std::vector<std::array<int, 30>> fa(n + 1, std::array<int, 30>{});

    for(int i = 2;i <= n;i ++) {
        std::cin >> fa[i][0];
    }

    for(int k = 1;k < 30;k ++) {
        for(int i = 1;i <= n;i ++) {
            fa[i][k] = fa[fa[i][k - 1]][k - 1];
        }
    }

    std::vector<int> vis(n + 1);
    vis[1] = true;
    vis[0] = true;

    auto get = [&](int st) -> int {
        for(int k = 29;k >= 0;k --) {
            if(vis[fa[st][k]])continue;
            st = fa[st][k];
        }

        return st;
    };

    int ans = -1;
    while(k --) {
        int u, l, r;std::cin >> u >> l >> r;
        if(ans != -1)continue;
        if(vis[u]) {
            ans = l;
            continue;
        }   

        for(int i = l;i <= r;i ++) {
            int now = get(u);
            vis[now] = true;
            if(now == u) {
                ans = i;
                break;
            }
        }
    }

    std::cout << ans << '\n';
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int t = 1;//std::cin >> t;
    while(t --) {
        returm();
    }
}

牛客多校4补题

B - BlindAlley

赛时战犯的一题,由于最大值自底向上转移的时候循环写反了,导致赛时没能开出,记录于此,警钟长鸣!

还是写写思路吧,这个题,其实关注点总结下来就一句话:一个点,是否有可能在某一时刻,以为能向右走过去走到终点,于是向右走到了这个点,但这个点实际上是无法抵达终点的,同时也没有回头路了。
翻译一下题意如下:

  1. 在某个点以为能向右走过去,翻译一下其实就是:当前列为 \(j\),向右走了过后,该右点能向右到达的最远点的列数 \(\geq j + k\)
  2. 这个点实际上是无法抵达终点,翻译一下,其实就是:从重点出发按题目规则反向走走不到这个点。

因此,思路就很简单了。
首先从起点跑一遍BFS再配合一次从右往左的简单DP转移最值,预处理一下每个点可以到达的最远列。
然后从重点跑一遍BFS,找出所有可以抵达的点。
然后再从起点跑一遍BFS,不过这次和第一次不一样,这次在走点的时候,要判断一下是否满足上述第1条题意翻译,然后对于可以抵达的点,判断一下它是不是可以抵达终点的点,如果不是,那就直接输出 Yes 即可。

点击查看代码
#include <bits/stdc++.h>
#define int long long

const int N = 1e7 + 9;

int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, 1, -1};

void returm() {
    int n, m, k;std::cin >> n >> m >> k;

    std::vector a(n, std::vector<char>(m));

    for(auto &x : a) {
        for(auto &c : x) {
            std::cin >> c;
        }
    }

    std::vector mx(n, std::vector<int>(m));

    std::queue<std::pair<int, int>> q;

    q.push({0, 0});
    std::vector vis(n, std::vector<int>(m));

    auto inmp = [&](int x, int y) -> bool {
        return x >= 0 && x < n && y >= 0 && y < m;
    };

    mx[0][0] = 0;
    while(q.size()) {
        auto [x, y] = q.front();
        q.pop();

        for(int i = 0;i < 3;i ++) {
            int nx = x + dx[i];
            int ny = y + dy[i];

            if(!inmp(nx, ny))continue;
            if(a[nx][ny] == '1')continue;
            if(vis[nx][ny])continue;

            mx[nx][ny] = std::max(y, ny);
            vis[nx][ny] = true;
            q.push({nx, ny});
        }
    }

    for(int j = m - 1;j >= 0;j --) {
        if(j + 1 < m) {
            for(int i = 0;i < n;i ++) {
                if(a[i][j + 1] == '0')mx[i][j] = std::max(mx[i][j], mx[i][j + 1]);
            }
        }

        for(int i = 0;i < n;i ++) {
            if(a[i][j] == '1')continue;
            if(i - 1 >= 0 && a[i - 1][j] == '0')mx[i][j] = std::max(mx[i][j], mx[i - 1][j]);
        }

        for(int i = n - 1;i >= 0;i --) {
            if(a[i][j] == '1')continue;
            if(i + 1 < n && a[i + 1][j] == '0')mx[i][j] = std::max(mx[i][j], mx[i + 1][j]);
        }
    }

    std::vector v(n, std::vector<int>(m));
    v[0][m - 1] = true;
    q.push({0, m - 1});

    while(q.size()) {
        auto [x, y] = q.front();
        q.pop();

        for(int i = 0;i < 4;i ++) {
            if(i == 2)continue;
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(!inmp(nx, ny))continue;
            if(a[nx][ny] == '1')continue;
            if(v[nx][ny])continue;
           
            v[nx][ny] = true;
            q.push({nx, ny}); 
        }
    }

    std::vector u(n, std::vector<int>(m));
    q.push({0, 0});
    u[0][0] = true;

    while(q.size()) {
        auto [x, y] = q.front();
        q.pop();

        if(!v[x][y]) {
            std::cout << "Yes\n";
            return;
        }

        for(int i = 0;i < 3;i ++) {
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(!inmp(nx, ny))continue;
            if(a[nx][ny] == '1')continue;
            if(u[nx][ny])continue;
            if(mx[nx][ny] < y + k)continue;

            u[nx][ny] = true;
            q.push({nx, ny});
        }
    }

    std::cout << "No\n";
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int t = 1;std::cin >> t;
    while(t --) {
        returm();
    }
}

G - Ghost intheParentheses

看到括号对,作为一个栈匹配的经典模型,常见的思路就是转化为前缀问题,对 ( 赋值为 \(1\),对 ) 赋值为 \(-1\)
题目说对于未知的括号对,要让它们的填法是唯一的,来满足合法的括号序列。
那考虑一下,什么样的括号对,如果未知,其填法不是唯一的呢?
对于一个合法的括号对序列,应该满足它的任何一个前缀和都 \(\geq 0\),如果交换一个左括号和一个右括号,该条件仍然满足,那么就说明不唯一!
因此,唯一的条件是,交换任何一个左括号和任何一个右括号,都会导致存在一个前缀值 \(< 0\)
回到这个题目,我们对于每一个点 \(i\),找到右边最近的一个满足前缀值 \(= 1\) 的位置 \(j\),这时候, \(i\) 左侧的左括号,都可以未知,且只能是左括号未知,\(j\) 右侧的右括号,都可以未知,且只能是右括号未知,因为此时交换左边未知左括号和右边未知的右括号,必然导致 \(j\) 处的前缀值变成 \(-1 < 0\)
同时,考虑 \(i\)\(j\) 之间的字符都已知,因为 \(i\)\(j\) 之间的字符如果未知,都会对 \(j\) 的位置造成影响,不便于计数。
由于选出的未知字符一定是左边很多左括号,右边很多右括号,不会出现先右括号再左括号的情况(出现这种情况了则可以交换他俩了,前缀值只会增大),这样的话,为了不重不漏,我们可以以每个左括号为分界点来进行计数,一定可以做到不重不漏。
但其实还是会漏掉一种情况,由于在上面的枚举过程中,我们保证了每种情况至少有一个左括号是不选为未知的,所以所有左括号都被选为未知的情况会被漏掉,并且这时候一定唯一,所以最后再加上这种情况即可。

点击查看代码
#include <bits/stdc++.h>
#define int long long

const int M = 998244353;

int power(int n, int m) {
    int res = 1;
    while(m) {
        if(m & 1)res = res * n % M;
        n = n * n % M;
        m >>= 1;
    }
    return res;
}

int inv(int x) {
    return power(x, M - 2) % M;
}

void returm() {
    std::string s;std::cin >> s;
    int n = s.size();

    s = ' ' + s;

    std::vector<int> pre(n + 2), suf(n + 2), sum(n + 2);

    for(int i = 1;i <= n;i ++) {
        sum[i] = sum[i - 1] + (s[i] == '(' ? 1 : -1);
    }

    for(int i = 1;i <= n;i ++) {
        pre[i] = pre[i - 1] + (s[i] == '(');
    }

    for(int i = n;i >= 1;i --) {
        suf[i] = suf[i + 1] + (s[i] == ')');
    }

    std::vector<int> p(n + 1);

    p[0] = 1;
    for(int i = 1;i <= n;i ++) {
        p[i] = p[i - 1] * 2 % M;
    }

    int ans = 0;
    for(int i = 1, j = 1;i <= n;i ++) {
        j = std::max(j, i);

        while(j <= n && sum[j] > 1)j ++;

        if(s[i] == '(') {
            ans = (ans + p[pre[i - 1]] * p[suf[j + 1]] % M) % M;
        }
    }

    ans = (ans + p[n / 2]) % M;
    ans = ans * inv(p[n]) % M;

    std::cout << (ans % M + M) % M << '\n';
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int t = 1;//std::cin >> t;
    while(t --) {
        returm();
    }
}

杭电多校2补题

杭电多校3补题

作者: 天天超方的

出处: https://www.cnblogs.com/TianTianChaoFangDe

关于作者:ACMer,算法竞赛爱好者

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显醒目位置给出, 原文链接 如有问题, 可邮件(1005333612@qq.com)咨询.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值