2024年码蹄杯本科组初赛 解题报告 | 珂学家


前言

在这里插入图片描述


题解

本科组的难度就很适宜,有区分度,做起来很舒服。
在这里插入图片描述

移动移动移动

难度:钻石
思路:组合数学

数学解
r e s = C ( x + y , x ) ∗ ( p / 100 ) x ∗ ( ( 100 − p ) / 100 ) y res= C(x+y, x) * (p/100)^x * ((100-p)/100)^y res=C(x+y,x)(p/100)x((100p)/100)y

变型得 r e s = C ( x + y , x ) ∗ p x ∗ ( 100 − p ) y / 10 0 ( x + y ) 变型得res = C(x+y, x) * p^x * (100-p)^y / 100^{(x+y)} 变型得res=C(x+y,x)px(100p)y/100(x+y)

import java.io.BufferedInputStream;
import java.util.Scanner;

public class Main {

    static boolean quick(int x1, int y1, int x2, int y2, int n) {
        if (x2 < x1 || y2 < y1) return false;
        int d = Math.abs(x2 - x1) + Math.abs(y2 - y1);
        if (d != n) return false;
        return true;
    }

    static long ksm(long b, long v, long m) {
        long r = 1;
        while (v > 0) {
            if (v % 2 == 1) {
                r = r * b % m;
            }
            b = b * b % m;
            v /= 2;
        }
        return r;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));

        int MX = 300000; // 3 * 10^5
        long mod = 998244353l;

        long[] fac = new long[MX + 1];
        long[] inv = new long[MX + 1];

        fac[0] = 1;
        for (int i = 1; i <= MX; i++) {
            fac[i] = fac[i - 1] * i % mod;
        }
        inv[MX] = ksm(fac[MX], mod - 2, mod);
        for (int i = MX - 1; i >= 0; i--) {
            inv[i] = inv[i + 1] * (i + 1) % mod;
        }

        int t = sc.nextInt();
        while (t-- > 0) {

            int x1 = sc.nextInt(), y1 = sc.nextInt();
            int x2 = sc.nextInt(), y2 = sc.nextInt();
            int n = sc.nextInt();

            int p = sc.nextInt(), q = sc.nextInt();

            if (!quick(x1, y1, x2, y2, n)) {
                System.out.println(0);
            } else {
                int dx = x2 - x1;
                int dy = y2 - y1;
                int ty = dy + dx;

                long fz = fac[ty] * inv[dx] % mod *inv[dy] % mod;
                long fz1 = ksm(p, dx, mod);
                long fz2 = ksm(q, dy, mod);

                fz = fz * fz1 % mod * fz2 % mod;

                long fm = ksm(100, ty, mod);
                long res = fz * ksm(fm, mod - 2, mod) % mod;
                System.out.println(res);
            }
        }
    }

}

请相信我会做图论

难度:钻石
思路:贪心

题目很俏皮,其他已经变相告诉你这题和图论算法没啥特别关系。


我会等差数列

难度: 钻石

思路: 前缀和


我会修改图

难度: 星耀

思路:逆序建边 + 并查集 + 启发式合并

对于拆边,可以逆向思维按建边来处理。

这样并查集为维护连通性,魔改并查集,引入启发式合并。

启发式合并的核心 : 大的合并小的 启发式合并的核心: 大的合并小的 启发式合并的核心:大的合并小的

import java.io.BufferedInputStream;
import java.util.*;
import java.util.stream.Collectors;

public class Main {

    static class Dsu {

        int n;
        int[] arr;

        Map<Integer, Integer> []hp;

        public Dsu(int n, int[] ws) {
            this.n = n;
            this.arr = new int[n + 1];
            hp = new Map[n + 1];
            for (int i = 1; i <= n; i++) {
                hp[i] = new TreeMap<>();
                hp[i].merge(ws[i], 1, Integer::sum);
            }
        }

        int find(int u) {
            if (arr[u] == 0) return u;
            return arr[u] = find(arr[u]);
        }

        void merge(int u, int v) {
            int a = find(u);
            int b = find(v);
            if (a != b) {
                // merge
                if (hp[a].size() >= hp[b].size()) {
                    int t = a;
                    a = b;
                    b = t;
                }
                for (Map.Entry<Integer, Integer> env: hp[a].entrySet()) {
                    hp[b].merge(env.getKey(), env.getValue(), Integer::sum);
                }
                arr[a] = b;
            }
        }

        int query(int p, int v, int y) {
            int other = y - v;
            if  (other != v) {
                return hp[find(p)].getOrDefault(other, 0);
            } else {
                return hp[find(p)].getOrDefault(other, 0) - 1;
            }
        }

    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));

        int n = sc.nextInt(), m = sc.nextInt(), q = sc.nextInt();

        int[] arr = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = sc.nextInt();
        }

        boolean[] vis = new boolean[m];
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int u = sc.nextInt(), v = sc.nextInt();
            edges.add(new int[] {u, v});
        }

        List<int[]> ops = new ArrayList<>();
        for (int i = 0; i < q; i++) {
            int op = sc.nextInt();
            if (op == 1) {
                int id = sc.nextInt() - 1;
                vis[id] = true; // del

                ops.add(new int[] {op, id});
            } else {
                int a = sc.nextInt();
                int b = sc.nextInt();
                ops.add(new int[] {op, a, b});
            }
        }

        Dsu dsu = new Dsu(n, arr);
        for (int i = 0; i < m; i++) {
            if (!vis[i]) {
                int[] e = edges.get(i);
                dsu.merge(e[0], e[1]);
            }
        }

        // 逆序处理
        List<Integer> res = new ArrayList<>();
        for (int i = ops.size() - 1; i >= 0; i--) {
            int[] cur = ops.get(i);
            if (cur[0] == 1) {
                int[] e = edges.get(cur[1]);
                dsu.merge(e[0], e[1]);
            } else {
                int a = cur[1];
                int y = cur[2];
                res.add(dsu.query(a, arr[a], y));
            }
        }
        Collections.reverse(res);

        System.out.println(res.stream().map(String::valueOf).collect(Collectors.joining("\n")));

    }
}

团队能量

难度: 钻石
思路: DP

这题很容易被带偏,以为是一道基于RMQ优化的DP

从转移公式入手

d p [ j ] = max ⁡ i = j − k i = j − 1 { d p [ i ] + m a x ( a r r [ i + 1 : j ] ) ∗ ( j − i ) − s u m ( a r r [ i + 1 : j ] ) } dp[j] = \max_{i=j-k}^{i=j-1} \{dp[i] + max(arr[i+1:j]) * (j - i) - sum(arr[i+1:j]) \} dp[j]=i=jkmaxi=j1{dp[i]+max(arr[i+1:j])(ji)sum(arr[i+1:j])}

如果引入前缀和 p r e [ i + 1 ] = p r e [ i ] + a r r [ i ] pre[i+1]=pre[i]+arr[i] pre[i+1]=pre[i]+arr[i]

d p [ j ] = max ⁡ i = j − k i = j − 1 { d p [ i ] + m a x ( a r r [ i + 1 : j ] ) ∗ ( j − i ) − ( p r e [ j + 1 ] − p r e [ i + 1 ] ) } dp[j] = \max_{i=j-k}^{i=j-1} \{dp[i] + max(arr[i+1:j]) * (j - i) - (pre[j+1] - pre[i + 1]) \} dp[j]=i=jkmaxi=j1{dp[i]+max(arr[i+1:j])(ji)(pre[j+1]pre[i+1])}

难在 m a x ( a r r [ i + 1 : j ] ) max(arr[i+1:j]) max(arr[i+1:j]), 它不能拆分,使得这个转化公式转发为

左侧和 j 有关 = 右侧和 i 有关的转移公式 左侧和j有关 = 右侧和i有关 的转移公式 左侧和j有关=右侧和i有关的转移公式

如果转化不了,则避免不了时间复杂度退化为 O ( n 2 ) O(n^2) O(n2)

所以这条路是行不通的。


回到 m a x ( a r r [ i + 1 : j ] − s u m ( a r r [ i + 1 : j ] ) 最小化这个核心问题 max(arr[i+1:j] - sum(arr[i+1:j]) 最小化这个核心问题 max(arr[i+1:j]sum(arr[i+1:j])最小化这个核心问题

是否存在优化的地方呢?

这里存在一个贪心策略,

即区间越小,拆分的越多, m a x ( a r r [ i + 1 : j ] − s u m ( a r r [ i + 1 : j ] ) 越小 即区间越小,拆分的越多,max(arr[i+1:j] - sum(arr[i+1:j])越小 即区间越小,拆分的越多,max(arr[i+1:j]sum(arr[i+1:j])越小

因此每个区间长度必然为[2, min(3, k)]之间

这样的话,这个DP就很容易写了。

核心在于发现

基于贪心优化的 D P 基于贪心优化的DP 基于贪心优化的DP

#include <bits/stdc++.h>

using namespace  std;

using int64 = long long;

int main() {

    int n, k;
    cin >> n >> k;
    vector<int64> arr(n);
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    sort(arr.begin(), arr.end());

    int64 inf = 0x3f3f3f3f3f3f3f;
    int r = min(k , 3);

    // 贪心即可
    vector<int64> dp(n + 1, inf);
    dp[0] = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 2; j <= r; j++) {
            if (i - j + 1 >= 0) {
                int64 mz = max(arr[i], arr[i - 1]);
                int64 acc = arr[i] + arr[i - 1];
                if (j > 2) {
                    mz = max(mz, arr[i - 2]);
                    acc += arr[i - 2];
                }
                dp[i + 1] = min(dp[i + 1], dp[i - j + 1] + j * mz - acc);
            }
        }
    }
    cout << (dp[n] == inf ? -1 : dp[n]) << endl;

    return 0;
}

异或

难度: 星耀

思路: 离线计算 + 树上差分/计数

对于任意二元组的异或和,需要使用到

拆位技巧 拆位技巧 拆位技巧


∑ i = 0 i = 30 ( z − z i ( 1 ) ) ∗ ( z − z i ( 0 ) ) ∗ ( 1 < < i ) \sum_{i=0}^{i=30} (z - z_i(1)) * (z - z_i(0)) * (1 << i) i=0i=30(zzi(1))(zzi(0))(1<<i)

至于不超过k距离的限制,这个属于树上差分的技巧


#include <bits/stdc++.h>

using namespace std;

struct T {
    T() {
        cnt = 0;
        memset(arr, 0, sizeof(arr));
    }
    void add(int v) {
        cnt += 1;
        for (int i = 0; i < 30; i++) {
            if ((v & (1<<i)) !=0) arr[i]++;
        }
    }
    void sub(int v) {
        cnt -= 1;
        for (int i = 0; i < 30; i++) {
            if ((v & (1<<i)) !=0) arr[i]--;
        }
    } 
    void merge(T &rhs) {
        cnt += rhs.cnt;
        for (int i = 0; i < 30; i++) {
            arr[i] += rhs.arr[i];
        }
    }
    long long compute(long long mod) {
        long long res = 0;
        for (int i = 0; i < 30; i++) {
            res += (long long)arr[i] * (cnt - arr[i]) % mod * (1ll << i) % mod;
            res %= mod;
        }
        return res;
    }
    int cnt;
    int arr[30];
};

int main() {

    int n, q, k;
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cin >> n >> q >> k;

    vector<int> w(n);
    vector<vector<int>> g(n);
    for (int &x: w) cin >> x;

    
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        g[u].push_back(v);
        g[v].push_back(u);
    }


    long long mod = (long long)1e9 + 7;
    vector<vector<int>> gx(n);
    vector<long long> ans(n);

    function<T(int,int,vector<int>&stk)> dfs;
    dfs = [&](int u, int fa, vector<int> &stk) {
        T res;
        res.add(w[u]);
        stk.push_back(u);
        for (int v: g[u]) {
            if (v == fa) continue;
            T cr = dfs(v, u, stk);
            res.merge(cr);
        }
        ans[u] = res.compute(mod);

        int nz = stk.size();
        if (nz - k - 1 >= 0) {  
            gx[stk[nz - k - 1]].push_back(u);
        }
        for (int son: gx[u]) {
            res.sub(w[son]);
        }
        stk.pop_back();
        return res;
    };

    vector<int> stk;
    dfs(0, -1, stk);
    
    for (int i = 0; i < q; i++) {
        int u;
        cin >> u;
        u--;
        cout << ans[u] << '\n';
    }

    return 0;
}

写在最后

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值