ZZJCACM个人训练赛23题解

原题链接
A
https://codeforces.com/contest/1999/problem/A 800
B
https://qoj.ac/contest/1794/problem/9310
C
https://codeforces.com/problemset/problem/2008/D 1100
D
https://qoj.ac/contest/1485/problem/8081
E
https://codeforces.com/contest/2002/problem/B 1000
F
https://codeforces.com/contest/1950/problem/F 1700
G
https://codeforces.com/contest/1954/problem/B 1200
H
https://codeforces.com/contest/1991/problem/D 1900
I
https://codeforces.com/contest/2020/problem/C 1400

A

太简单,直接看代码吧

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int a; cin >> a;
        cout << a / 10 + a % 10 << "\n";
    }
}

B

由于题目求的是方案数取余2的值,即求方案数的奇偶性。

尝试画一个矩阵来描述

\(M[i][j] = [j \in [l_i, r_i]] (即当 j \in [l_i, r_i],时M[i][j] = 1,否则为0)\)

以第一个样例为例:

\( \left[ \begin {array}{} 1 & 1 & 0 & 0 & 0 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ \end{array} \right] \)

我们把第二列除了最后一行的1全改为0,值是不变的,这由于第五行只能选择2

手模一下可以推广,初等行变换不会影响最后的奇偶性

注意到,当有行全为0的时候,那么答案就是偶数,反之为奇数

即判断这个矩阵是否行满秩即可

我们考虑区间覆盖,问题转化为判断是否存在一个区间能被其他若干个区间覆盖

我们把l和r + 1建边,如果图中有环,则说明存在一个区间能被其他若干个区间覆盖

下面我用拓扑排序判环

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n;
        cin >> n;
        vector<vector<int>> adj(n + 2, vector<int>(0));
        vector<int> d(n + 2);
        auto addEdge = [&](int u, int v) {
            adj[u].emplace_back(v);
            adj[v].emplace_back(u);
            d[u]++; d[v]++;
        };
        auto toposort = [&](vector<vector<int>> &adj, vector<int> &d) {
            vector<int> tp;
            queue<int> q;
            for (int i = 1; i <= n + 1; ++ i) {
                if (d[i] == 1) q.push(i);
            }
            while (q.size()) {
                int x = q.front(); q.pop();
                tp.push_back(x);
                for (auto y : adj[x]) {
                    d[y] --; d[x] --;
                    if (d[y] == 1) q.push(y);
                }
            }
            return (int)tp.size() == n + 1;
        };
        for (int i = 1; i <= n; ++ i) {
            int l, r;
            cin >> l >> r;
            addEdge(l, r + 1);
        }
        cout << toposort(adj, d) << "\n";
    }
}

C

由于原数组是排列,每一个点经过若干次,一定可以到本身,那么 \(F(i)\) 其实就是 \(i\) 所在的联通块的黑色的个数

我们用并查集合并即可得到连通块,把并查集的中的每个的点的权提前赋好再去合并

最后输出对应的连通块的值即可

#include <bits/stdc++.h>
using namespace std;
class DSU {
public:
    int n;
    vector<int> dsu, dsus;
    // dsu[i]存是 i 的祖宗结点
    // dsus[i]存 i 所在图的大小
    DSU() {}
    DSU(int n) { init(n); }
    void init(int n) {
        this -> n = n;
        dsu.resize(n + 1);
        dsus.resize(n + 1, 1);
        iota(dsu.begin(), dsu.end(), 0);
    }
    int find(int a) {
        if (a == dsu[a]) {
            return a;
        } else {
            dsu[a] = find(dsu[a]);
            return dsu[a];
        }
    }
    void merge(int a, int b) {
        int x = find(a);
        int y = find(b);
        if (x != y) {
            if (dsus[x] < dsus[y]) { swap(x, y); }
            dsu[y] = x; dsus[x] += dsus[y];
        }
    }
    //由于未必路径压缩好了,所以求祖宗结点需调用find函数或者使用reboot()函数路径压缩
    void reboot() {
        for (int i = 1; i <= n; ++ i) {
            int fa = find(dsu[i]);
            dsu[i] = fa; dsus[i] = dsus[fa];
        }
    }
};

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n;
        cin >> n;
        vector<int> p(n + 1);
        for (int i = 1; i <= n; ++ i) cin >> p[i];
        DSU a(n);
        for (int i = 1; i <= n; ++ i) {
            char c;
            cin >> c;
            a.dsus[i] = (c - '0') ^ 1;
        }
        for (int i = 1; i <= n; ++ i) {
            a.merge(i, p[i]);
        }
        a.reboot();
        for (int i = 1; i <= n; ++ i) {
            cout << a.dsus[i] << " \n"[i == n];
        }
    }
}

D

题目求 \(C_2\) 上的点到 \(C_1\) 上所有点的最小期望曼哈顿距离

\(C_2\) 上的点 \((x_0, y_0)\) 为使得 \(C_2\) 上的点到 \(C_1\) 上所有点的最小期望曼哈顿距离的点

\(EMD\)\(C_1\)\((x_0, y_0)\) 期望曼哈顿距离
\(MD(x, y)\)\((x, y)\)\((x_0, y_0)\) 曼哈顿距离

对于 \(C_1\) 上的点 \((x_1 + \Delta x, y_1 + \Delta y)\) 必然有 \((x_1 - \Delta x, y_1 - \Delta y)\) 使得

\(MD(x_1 + \Delta x, y_1 + \Delta y) = MD(x_1 - \Delta x, y_1 - \Delta y)\)

所以 \(EMD = MD(x_1, y_1)\)

即到 \((x_0, y_0)\) 期望曼哈顿距离就是 \(C_1\) 的圆心到 \((x_0, y_0)\) 的曼哈顿距离

那么我们只需要最小化这个曼哈顿距离即可

不妨反过来思考,我们在 \(C_2\) 上找一个点使得曼哈顿距离最小

\(C_1\) 的圆心为圆心画曼哈顿圆 \(MC_1\) ,当 \(MC_1\)\(C_2\) 相切时,切点就是 \((x_0, y_0)\)

然后计算 \(MD(x_1, y_1, x_0, y_0)\) 即可

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        ll x11, y11, x12, y12;
        ll x21, y21, x22, y22;
        cin >> x11 >> y11 >> x12 >> y12;
        cin >> x21 >> y21 >> x22 >> y22;
        double x10 = (double)(x11 + x12) / 2.0, y10 = (double)(y11 + y12) / 2.0;
        double x20 = (double)(x21 + x22) / 2.0, y20 = (double)(y21 + y22) / 2.0;
        double d = sqrt(2ll * ((x22 - x21) * (x22 - x21) + (y22 - y21) * (y22 - y21))) / 4.0;
        double x0, y0;
        if (x10 < x20 && y10 < y20) {
            x0 = x20 - d;
            y0 = y20 - d;
        } else if (x10 < x20 && y10 > y20) {
            x0 = x20 - d;
            y0 = y20 + d;
        } else if (x10 > x20 && y10 < y20) {
            x0 = x20 + d;
            y0 = y20 - d;
        } else if (x10 > x20 && y10 > y20) {
            x0 = x20 + d;
            y0 = y20 + d;
        }
        double ans = abs(x0 - x10) + abs(y0 - y10);
        cout << fixed << setprecision(10) << ans << "\n";
    }
}

E

推理可得,答案和Alice的选择无关,那么我们只需要遍历Alice的数组,每次判断当前的首尾是不是也是Bob数组的首尾即可

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n;
        cin >> n;
        vector<int> a(n + 1), b(n + 1);
        for (int i = 1; i <= n; ++ i) cin >> a[i];
        for (int i = 1; i <= n; ++ i) cin >> b[i];
        bool flag = 1;
        int l = 1, r = n;
        for (int i = 1; i <= n; ++ i) {
            if (a[i] == b[l] && a[n] == b[r]) {
                l ++;
            } else if (a[i] == b[r] && a[n] == b[l]) {
                r --;
            } else {
                flag = 0;
                break;
            }
        }
        if (flag) {
            cout << "Bob\n";
        } else {
            cout << "Alice\n";
        }
    }
}

F

根据二叉树的性质, \(n0 == n2 + 1\) ,若不符合则输出 \(-1\)

考虑贪心,优先把度为2的用完,再用1的

我们用队列模拟这个贪心的过程,队首为 \(x\) ,表示当前在第 \(x\)

所以如果需要往队列里添加节点的话 需要添加 \(x + 1\)

\(ans\) 记录最大层数即可

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n2, n1, n0;
        cin >> n2 >> n1 >> n0;
        int ans = -1;
        if (n0 == n2 + 1) {
            queue<int> q;
            q.push(0);
            ans = 0;
            while (!q.empty()) {
                int x = q.front(); q.pop();
                ans = x;
                if (n2) {
                    n2 --;
                    q.push(x + 1);
                    q.push(x + 1);
                } else if (n1) {
                    n1 --;
                    q.push(x + 1);
                }
            }
        }
        cout << ans << "\n";
    }
}

G

分析题目可得,由于原数组是“美丽”的,那么数组的首尾一定是相等的,并且其中有其他数字,也只是一个孤立的,因为如果有两个及以上,那么该数组就不“美丽”,自然地,只有把两个孤立的数字合并就可以了

要使两个孤立的数字合并,那么只需要删掉他们之间的所有数字就行
问题就被转化成了,最小的连续为k的子段,k即是数组头部的位置
注意当全部为一种时,要输出-1

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n; 
        cin >> n;
        int ans = n, cur = 0;
        vector<int> a(n + 1);
        for (int i = 1; i <= n; ++ i) cin >> a[i];
        for (int i = 1; i <= n; ++ i) {
            if (a[i] != a[1]) {
                ans = min(ans, cur);
                cur = 0;
            } else {
                cur ++;
            }
        }
        ans = min(ans, cur);
        if (ans == n) ans = -1;
        cout << ans << "\n";
    }
}

H

看到要使直接连接的两个顶点没有相同的颜色,可以想到著名的四色问题
猜测至多只需要四种颜色就能填满
自然的,(在结点较多时)
我们给结点 1 染色 1
我们给结点 2 染色 2
我们给结点 3 染色 3
我们给结点 4 染色 4
5 可以选择 1 3 4
6 可以选择 2
7 可以选择 1 3
8 可以选择 1 2 4

目前不能找到什么规律,但是由于建边是根据异或的,我们可以尝试把5 6 7 8与其可以建边的点进行异或看看
5:
1 4
3 6
4 1
6:
2 4
7:
1 6
3 4
8:
1 9
2 10
4 12
可以发现5 6 7的异或值都有4
进一步思考

$xor(4n_1+k, 4n_2+k) = 4n (k \in {0, 1, 2, 3}, n \in N^*) $

\(4n+k\) 可以选用同一种颜色,因为 \(4n\) 一定是一个非质数,即这类点不会直接相连
由于题目需要输出最小的,将颜色不需要4种的特判即可

#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        int n;
        cin >> n;
        if (n < 6) {
            cout << n / 2 + 1 << "\n";
            int ans = 1, t = 1;
            for (int i = 1; i <= n; ++ i) {
                cout << ans << " \n"[i == n];
                ans += t % 2;
                t = (t + 1) % 2;
            }
        } else {
            cout << 4 << "\n";
            for (int i = 1; i <= n; ++ i) {
                cout << (i - 1) % 4 + 1 << " \n"[i == n];
            }
        }
    }
}

I

我们将 \(a, b, c, d\) 全部二进制展开

对每一位有

a a a a a
b 0 0 1 1
c 0 1 0 1
ans a 0 1 !a

按照这个表算出 \(a\) 即可

更进一步,我们发现 \(a = xor(b, d)\)

然后判断 \(a\) 是否合法即可

下面给出两种解法

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        ll a, b, c, d;
        cin >> b >> c >> d;
        a = b ^ d;
        if ((a | b) - (a & c) != d) a = -1;
        cout << a << "\n";
    }
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    int tt = 1;
    cin >> tt;
    while (tt--) {
        bool flag = 1;
        ll a = 0, b, c, d;
        cin >> b >> c >> d;
        ll B = b, C = c, D = d;
        vector<int> al(65);
        for (int i = 0; i < 65; ++ i) {
            if (b % 2ll == c % 2ll) {
                al[i] = (d % 2ll) ^ (b % 2ll);
            } else if (b % 2ll ==  0 && c % 2ll == 1) {
                al[i] = 1;
            } else if (b % 2ll ==  1 && c % 2ll == 0) {

            } else {
                flag = 0;
                break;
            }
            b /= 2ll;
            c /= 2ll;
            d /= 2ll;
            if (b == 0 && c == 0 && d == 0) break;
        }
        for (int i = 64; i >= 0; -- i) {
            a = a * 2ll + al[i];
        }
        if ((a | B) - (a & C) != D) a = -1;
        cout << a << "\n";
    }
}
posted @ 2024-12-23 20:35  Proaes  阅读(95)  评论(0)    收藏  举报