比赛链接

A

题解

方法一

知识点:并查集,树形dp,背包dp。

因为需要路径中的最大值,因此考虑按边权从小到大加入图中,保证通过这条边产生贡献的点对已经全部出现。

在加边的同时进行树上背包,答案存在集合根节点里即可。

树上背包需要用到上下界限制的转移优化,能将复杂度从 \(O(n^3)\) 降到 \(O(n^2)\) ,基本思想是每个点对只在LCA处贡献一次。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n^2)\)

方法二

知识点:Kruskal重构树,树形dp,背包dp。

对原图进行最小生成树性质的Kruskal重构,任意两点的LCA点权为路径最大值。

然后,在这棵重构树上进行dp,核心内容一致,区别在于原边权变为了点权,原子树大小变为叶子节点个数。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n^2)\)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct DSU {
    vector<int> fa;
    vector<int> sz;

    DSU(int n = 0) { init(n); }

    void init(int n) {
        fa.assign(n + 1, 0);
        sz.assign(n + 1, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

    bool same(int x, int y) { return find(x) == find(y); }

    void merge(int x, int y) {
        sz[y] += sz[x];
        fa[find(x)] = find(y);
    }
};

tuple<int, int, int> e[3007];
bool black[3007];
int cost[3007];

ll f[3007][3007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> black[i];
    for (int i = 1;i <= n;i++) {
        cin >> cost[i];
        f[i][black[i]] = 0;
        f[i][black[i] ^ 1] = -cost[i];
    }
    for (int i = 1;i <= n - 1;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = { w,u,v };
    }

    sort(e + 1, e + n);
    DSU dsu(n);
    for (int i = 1;i <= n;i++) {
        auto [w, u, v] = e[i];
        u = dsu.find(u);
        v = dsu.find(v);
        vector<ll> g(n + 1, -1e18);
        for (int j = 0;j <= dsu.sz[u];j++) {
            for (int k = 0;k <= dsu.sz[v];k++) {
                ll val = 1LL * (j * (dsu.sz[v] - k) + k * (dsu.sz[u] - j)) * w;
                g[j + k] = max(g[j + k], f[u][j] + f[v][k] + val);
            }
        }
        for (int j = 0;j <= dsu.sz[u] + dsu.sz[v];j++) f[u][j] = g[j];
        dsu.merge(v, u);
    }
    ll ans = 0;
    for (int i = 0;i <= n;i++) ans = max(ans, f[dsu.find(1)][i]);
    cout << ans << '\n';
    return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct DSU {
    vector<int> fa;
    vector<int> sz;

    DSU(int n = 0) { init(n); }

    void init(int n) {
        fa.assign(n + 1, 0);
        sz.assign(n + 1, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

    bool same(int x, int y) { return find(x) == find(y); }

    void merge(int x, int y) {
        sz[y] += sz[x];
        fa[find(x)] = find(y);
    }
};

int n;
struct edge {
    int u, v, w;
}e[3007];
bool black[3007];
int cost[3007];

int g_w[6007];
vector<int> g[6007];
DSU dsu;
void kruskal_rebuild() {
    sort(e + 1, e + n, [&](const edge &a, const edge &b) {return a.w < b.w;});
    dsu.init(2 * n - 1);
    for (int i = 1;i <= n - 1;i++) {
        auto [u, v, w] = e[i];
        u = dsu.find(u);
        v = dsu.find(v);
        g_w[n + i] = w;
        g[n + i].push_back(u);
        g[n + i].push_back(v);
        dsu.merge(u, n + i);
        dsu.merge(v, n + i);
    }
}
/// kruskal重构树,O(mlogm),图重构为树后任意两点LCA的权值是路径瓶颈
//* 最小生成树 <=> u-v所有路径最大边权中的最小值

ll sz[6007], f[6007][3007];
void dfs(int u) {
    sz[u] = u <= n;
    for (auto v : g[u]) {
        dfs(v);
        vector<ll> ff(n + 1, -1e18);
        for (int i = 0;i <= sz[u];i++) {
            for (int j = 0;j <= sz[v];j++) {
                ll val = 1LL * (i * (sz[v] - j) + j * (sz[u] - i)) * g_w[u];
                ff[i + j] = max(ff[i + j], f[u][i] + f[v][j] + val);
            }
        }
        for (int i = 0;i <= sz[u] + sz[v];i++) f[u][i] = ff[i];
        sz[u] += sz[v];
    }
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> black[i];
    for (int i = 1;i <= n;i++) {
        cin >> cost[i];
        f[i][black[i]] = 0;
        f[i][black[i] ^ 1] = -cost[i];
    }
    for (int i = 1;i <= n - 1;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = { u,v,w };
    }

    kruskal_rebuild();
    dfs(2 * n - 1);

    ll ans = 0;
    for (int i = 0;i <= n;i++) ans = max(ans, f[2 * n - 1][i]);
    cout << ans << '\n';
    return 0;
}

B

题解

知识点:排列组合。

只有大小相同的多重子集才能产生贡献。对于一对子集,显然从小到大排序后,对应数字的差的绝对值的和就是最小操作次数,现在考虑枚举每个点对产生的贡献。

对于一组点对 \((i,j)\) 表示A中第 \(i\) 个数和B中第 \(j\) 个数在对应位置,那么包含它们的子集有:

\[\begin{aligned}
\left( \sum_{k = 0}^{\min(i-1,j-1)} \dbinom{i-1}{k} \dbinom{j-1}{k} \right) \cdot \left( \sum_{k = 0}^{\min(n-i,n-j)} \dbinom{n-i}{k} \dbinom{n-j}{k} \right)
\end{aligned}
\]

直接算是 \(O(n)\) 的,无法预处理,这里需要用到范德蒙德卷积公式:

\[\begin{aligned}
\sum_{i = 0}^{k} \dbinom{n}{i} \dbinom{m}{k-i} = \dbinom{n+m}{k}
\end{aligned}
\]

其中一个推论是:

\[\begin{aligned}
\sum_{i = 0}^{m} \dbinom{n}{i} \dbinom{m}{m-i} = \sum_{i = 0}^{m} \dbinom{n}{i} \dbinom{m}{i} = \dbinom{n+m}{m}
\end{aligned}
\]

因此原式可以化简为 \(O(1)\) 的计算。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int P = 998244353;
namespace Number_Theory {
    const int N = 4e3 + 7;
    int qpow(int a, ll k) {
        int ans = 1;
        while (k) {
            if (k & 1) ans = 1LL * ans * a % P;
            k >>= 1;
            a = 1LL * a * a % P;
        }
        return ans;
    }
    int fact[N], invfact[N];
    void init(int n) {
        fact[0] = 1;
        for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
        invfact[n] = qpow(fact[n], P - 2);
        for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
    }
}
namespace CNM {
    using namespace Number_Theory;
    int C(int n, int m) {
        if (n == m && m == -1) return 1; //* 隔板法特判
        if (n < m || m < 0) return 0;
        return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
    }
}
/// 公式法求组合数,O(n),预处理阶乘及其逆元快速求出组合数

int a[2007], b[2007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i];
    for (int i = 1;i <= n;i++) cin >> b[i];
    sort(a + 1, a + n + 1);
    sort(b + 1, b + n + 1);
    CNM::init(2 * n);
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n;j++) {
            int val = abs(a[i] - b[j]);
            (ans += 1LL * CNM::C(i + j - 2, min(i, j) - 1) * CNM::C(2 * n - i - j, min(n - i, n - j)) % P * val % P) %= P;
        }
    }
    cout << ans << '\n';
    return 0;
}

C

题解

知识点:数学。

显然,\(2\) 的个数远比 \(5\) 多,因此我们只需要计算 \(5\) 因子的个数即可。

我们化简后有如下式子:

\[\begin{aligned}
\prod_{i=1}^n i!! = 1^{\left\lceil \frac{n}{2} \right\rceil} \cdot 2^{\left\lfloor \frac{n}{2} \right\rfloor} \cdot 3^{\left\lceil \frac{n}{2} \right\rceil - 1} \cdot 4^{\left\lfloor \frac{n}{2} \right\rfloor - 1} \cdots
\end{aligned}
\]

注意到, \(5\) 的倍数会贡献一次, \(5^2\) 的倍数又会贡献一次,以此类推。因此,我们按 \(5\) 的幂求幂次数总和即可。

但是,这里奇数和偶数的幂次规律是不同的,但都是等差,分别求一下即可。例如, \(5\) 的倍数时,分别求 \(5,15,25,\cdots\)\(10,20,30,\cdots\) 两个幂次等差数列的和即可。

时间复杂度 \(O(\log n)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128_t;

template<class T>
inline void write(T x) {
    if (x < 0) { putchar('-');x = -x; }
    if (x >= 10) write(x / 10);
    putchar(x % 10 + '0');
}

i128 calc1(i128 n, i128 x) {
    i128 a1 = (n + 1) / 2 - x / 2;
    i128 an = a1 % x;
    i128 cnt = (a1 - an) / x + 1;
    return (a1 + an) * cnt / 2;
}

i128 calc0(i128 n, i128 x) {
    i128 a1 = n / 2 - x + 1;
    i128 an = a1 % x;
    i128 cnt = (a1 - an) / x + 1;
    return (a1 + an) * cnt / 2;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll n;
    cin >> n;
    i128 x = 5, ans = 0;
    while (n >= x) {
        if (n >= 2 * x) ans += calc0(n, x);
        ans += calc1(n, x);
        x *= 5;
    }
    write(ans);
    puts("");
    return 0;
}

E

题解

知识点:枚举,前缀和。

我们求出前缀和 \(sum\),那么原式改写为 \(sum_{b_1} - sum_{l-1},sum_{b_2} - sum_{b_1} \cdots\) 。显然,当 \(sum_{l-1}\)\(sum_{r}\) 不同奇偶时无解,否则需要在 \([l,r-1]\) 的前缀和之间找到 \(k-1\) 个和 \(sum_{l-1},sum_{r}\) 同奇偶的位置,这个过程可以用 \(cnt\) 记录前缀和奇偶个数的前缀和,可以 \(O(1)\) 查询。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll a[100007];
int cnt[100007];
bool solve() {
    int n, q;
    cin >> n >> q;
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
        a[i] += a[i - 1];
        cnt[i] = (a[i] & 1) + cnt[i - 1];
    }
    while (q--) {
        int l, r, k;
        cin >> l >> r >> k;
        if ((a[l - 1] & 1) != (a[r] & 1)) {
            cout << "NO" << '\n';
            continue;
        }
        int res = cnt[r - 1] - cnt[l - 1];
        if (!(a[l - 1] & 1)) res = r - l - res;
        if (res >= k - 1) cout << "YES" << '\n';
        else cout << "NO" << '\n';
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

G

题解

知识点:GCD和LCM。

可以分三类讨论:

  1. \(x,y = 0\) 时,当且仅当 \(z = 0\) 时有解。
  2. 否则,当 \(x = 0\)\(y = 0\) 时,当且仅当 \(z\) 为其中非零数的倍数时有解。
  3. 否则,当且仅当 \(z\)\(0\) 或者 \(\gcd(x,y)\) 的倍数时有解。

时间复杂度 \(O(T\log{\min\{x_i,y_i\}})\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int x, y, z;
    cin >> x >> y >> z;
    int d = gcd(x, y);
    if (x == 0 && y == 0) {
        if (z == 0) cout << "YES" << '\n';
        else cout << "NO" << '\n';
    }
    else if (x == 0 || y == 0) {
        if (z % d == 0) cout << "YES" << '\n';
        else cout << "NO" << '\n';
    }
    else {
        if (z && z % d == 0) cout << "YES" << '\n';
        else cout << "NO" << '\n';
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}