abc392题解

log

25-2-26 新增了 G 题的题解

ABC

略(C其实就是阅读理解)

D

题目大意

\(n\) 个骰子中指定两个骰子,问这两个骰子骰到同一个数的概率最大是多少

解题思路

直接暴力就好了:枚举每一个数,对每个数枚举每一对骰子。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::vector d(n, std::vector(M + 1, 0));
    for (int i = 0; i < n; i++) {
        std::cin >> d[i][0];
        for (int j = 0; j < d[i][0]; j++) {
            int a = 0;
            std::cin >> a;
            d[i][a]++;
        }
    }

    std::vector cnt(n, std::vector(n, 0ll));
    for (int k = 1; k <= M; k++) {
        for (int i = 0; i < n; i++) {
            if (d[i][k] == 0) {
                continue;
            }
            for (int j = i + 1; j < n; j++) {
                if (d[j][k] == 0) {
                    continue;
                }
                cnt[i][j] += 1ll * d[i][k] * d[j][k];
            }
        }
    }
    
    double ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            ans = std::max(ans, 1.0 * cnt[i][j] / (1ll * d[i][0] * d[j][0]));
        }
    }
    std::cout << std::fixed << std::setprecision(9) << ans << '\n';
}

E

题目大意

在一个图中修改最少的边,使得整张图连通。每条边只能被修改一次,而且只能修改其中一个端点。保证能够通过修改边的端点让整张图连通。

解题思路

用并查集记录连通块。在读入边时,记录下对连通性没有影响的边(即两端点在同一个连通块里的边),这些边就是我们可以拿来做修改的边。

当我们知道图中一共有多少个连通块时,我们就知道了至少要修改都少条边(把连通块看成点就好了)。此后我们在从先前记录下的边中去边,每条边都可以修改成连接两个连通块的边。

CODE
void solve()
{
    int n = 0, m = 0;
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }

    std::vector<std::array<int, 2>> e;
    for (int i = 1; i <= m; i++) {
        int u = 0, v = 0;
        std::cin >> u >> v;
        if (not merge(u, v)) {
            e.push_back({ i, u });
        }
    }

    std::set<int> root;
    for (int i = 1; i <= n; i++) {
        if (fa[i] == i) {
            root.insert(i);
        }
    }
    std::cout << root.size() - 1 << '\n';
    for (auto &[idx, v] : e) {
        if (root.size() == 1) {
            break;
        }
        root.erase(get(v));
        std::cout << idx << ' ' << v << ' ' << (*root.begin()) << '\n';
        merge((*root.begin()), v);
    }

    return;
}

F

题目大意

给出一个空序列,然后依次插入 1 到 \(n\),并且第 \(i\) 个数要是当前序列的第 \(p_i\) 个数,求最后得到的序列。

解题思路

从小到大考虑的话涉及到的插入太麻烦了。所以我们从大到小考虑删数,维护一个数组 \(T\)\(T_i = 1\) 表示第 \(i\) 个位置上有一个数;\(T_i = 0\) 表示第 \(i\) 个位置上没有数。对于有数的位置,一开始我们并不知道具体是那个数在那里,但是我可以知道每个 1 是第几个 1(区间求和就好了)。根据 1 的排名,我们就可以从从大往小依次确定每个数的位置,在确定一个数的位置后,我们就把该数的位置置为 0 表示我们删去了该数。所以我们需要一个支持单点修改和区间求和的数据结构来代替 \(T\)

CODE
class BIT {
private:
    int n;
    std::vector<int> a;

    int lowbit(int x) {
        return x & -x;
    }
    int sum(int l) {
        int res = 0;
        for (int i = l; i >= 1; i -= lowbit(i)) {
            res += a[i];
        }
        return res;
    }
public:
    BIT(int _n) {
        n = _n;
        a.assign(n + 1, 0);
    }
    void add(int pos, int val) {
        for (int i = pos; i <= n; i += lowbit(i)) {
            a[i] += val;
        }
    }
    int sum(int l, int r) {
        return sum(r) - sum(l - 1);
    }
};

void solve()
{
    int n = 0;
    std::cin >> n;
    BIT tr(n);
    std::vector p(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        tr.add(i, 1);

        std::cin >> p[i];
    }

    std::vector ans(n + 1, 0);
    for (int i = n; i >= 1; i--) {
        int l = 1, r = n;
        while (l <= r) {
            int m = l + r >> 1;
            if (tr.sum(1, m) < p[i]) {
                l = m + 1;
            }
            else {
                r = m - 1;
            }
        }
        tr.add(l, -1);
        ans[l] = i;
    }
    
    for (int i = 1; i <= n; i++) {
        std::cout << ans[i] << " \n"[i == n];
    }
    return;
}

G

题目大意

定义三元组 \(<A,B,C>\) 为好的当且仅当这个三元组满足以下条件:

  1. \(A < B < C\)
  2. \(B - A = C - B\)

给出 \(N\) 个互不相同且不超过 \(10^6\) 的数,问这些数中共能选出多少不同的好三元组

解题思路

在好三元组中有 \(2B = A + C\),于是对于每个 \(B\) 我们就去找有多少对 \(A,C\) 满足 \(2B = A + C\)

现在假设我们有一个多项式,这个多项式第 \(i\) 次项的系数为 \(1\) 当且仅当 \(i\) 是题中给出的数中的一个,否则为 \(0\)。于是对这个多项式平方后,\(2B\) 次项的系数就代表有多少对有序的 \(A\)\(C\) 满足 \(2B = A + C\),减去 \(A = C = B\) 的情况再除 2 就得到了以 \(B\) 为最中间那个数的好三元组的数量

于是就可以直接用多项式乘法来做这道题了。(自己写的多项式乘法

CODE
constexpr int N = 1e5, M = 1e6, Inf = 1e9;

std::vector<int> idx(M << 2);
void fft(int len, std::vector<std::complex<double>> &A, int inv) {
    for (int i = 1; i < len; i++) {
        if (i < idx[i]) {
            std::swap(A[i], A[idx[i]]);
        }
    }

    for (int mid = 1; mid < len; mid <<= 1) {
        std::complex<double> unit(cos(M_PI / mid), inv * sin(M_PI / mid));
        for (int l = 0; l < len; l += (mid << 1)) {
            std::complex<double> w(1.0, 0.0);
            for (int i = 0; i < mid; i++, w *= unit) {
                std::complex<double> x(A[i | l]), y(w * A[i | l | mid]);
                A[i | l] = x + y;
                A[i | l | mid] = x - y;
            }
        }
    }

    for (int i = 0; inv == -1 && i < len; i++) {
        A[i] /= len;
    }
    return;
}

std::vector<i64> converlution(std::vector<i64> &a, std::vector<i64> &b) {
    int la = a.size() - 1, lb = b.size() - 1;
    int len = 1;
    // 长度是 2 的幂且必须大于最高次数
    while (len <= la + lb) {
        len <<= 1;
    }
    
    for (int l = 1, r = 1, bit = len >> 1; l < len; l <<= 1, bit >>= 1) {
        for (int i = 0; i < l; i++) {
            idx[r++] = idx[i] | bit;
        }
    }

    std::vector<std::complex<double>> A(len);
    for (int i = 0; i < len; i++) {
        A[i] = std::complex<double>(i <= la ? a[i] : 0.0, i <= lb ? b[i] : 0.0);
    }
    fft(len, A, 1);
    for (int i = 0; i < len; i++) {
        A[i] = A[i] * A[i];
    }
    fft(len, A, -1);

    std::vector res(la + lb + 1, 0ll);
    for (int i = 0; i <= la + lb; i++) {
        res[i] = i64(A[i].imag() / 2.0 + 0.5);
    }
    return res;
}

void solve()
{
    int n = 0;
    std::cin >> n;
    std::vector p(M + 1, 0ll);
    for (int i = 0; i < n; i++) {
        int a = 0;
        std::cin >> a;
        p[a] = 1;
    }

    i64 res = 0;
    auto q = converlution(p, p);
    for (int i = 1; i <= M; i++) {
        if (p[i] == 1) {
            res += (q[2 * i] - 1) / 2;
        }
    }
    std::cout << res << '\n';
    return;
}
posted @ 2025-02-17 20:32  Young_Cloud  阅读(26)  评论(0)    收藏  举报