2025.8.24模拟赛

T1

\(n\) 个点的无向图,每条边有一个长度(经过这条边需要花费的时间),每个点上都有一个红绿灯,第 \(i\) 个点上的红绿灯周期为 \(t_i\),每个周期内只有从 \(s_i\)\(e_i\) 的时间内可以从这个节点通行。形式化的,若你到达第 \(i\) 个节点的时间为 \(k\),你能从这个节点通行当且仅当 \(s_i ≤(k\bmod t_i)≤e_i\)。你需要求出,假设从时刻 \(0\) 出发(即所有红绿灯都处于周期的开始阶段),从起点 \(S\) 到终点 \(T\) 所花费最少的时间是多少。
\(1\le n\le 10^5,1\le m\le 10^6\)

反正早到没坏处,dij 的时候换一下边权即可。

赛时代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5, M = 2e6 + 5;
int n, m, S, T;
int t[N], s[N], e[N];
int vis[N], head[N], tot;
ll dis[N];
struct edge {
    int v, w, nxt;
} g[M];
void add(int u, int v, int w) {
    g[++tot] = (edge){v, w, head[u]}, head[u] = tot;
    g[++tot] = (edge){u, w, head[v]}, head[v] = tot;
}
priority_queue<pair<ll, int>> q;
ll dij() {
    for (int i = 1; i <= n; ++i)
        dis[i] = 1e18;
    dis[S] = 0, q.push(make_pair(0, S));
    while (q.size()) {
        int u = q.top().second;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (int i = head[u]; i; i = g[i].nxt) {
            int v = g[i].v, w = g[i].w;
            ll d = dis[u] + w;
            if (d % t[v] > e[v])
                d += t[v] - d % t[v] + s[v];
            else if (d % t[v] < s[v])
                d += s[v] - d % t[v];
            if (d < dis[v])
                dis[v] = d, q.push(make_pair(-dis[v], v));
        }
    }
    return dis[T];
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m >> S >> T;
    for (int i = 1; i <= n; ++i)
        cin >> t[i] >> s[i] >> e[i];
    for (int i = 1, u, v, w; i <= m; ++i)
        cin >> u >> v >> w, add(u, v, w);
    cout << dij() << '\n';
    return 0;
}

T2

有一个 \(n\times n\) 的网格,每个格子里可以填一个非负整数 \(a_{i,j}\)。现给定两个长度为 \(n\) 的非负整数序列 \(\{R_i\}\)\(\{C_j\}\),你需要保证填入的数字每一行的和为 \(R_i\),每一列的和为 \(C_j\),且最小化 \(\sum_{i,j}a_{i,j}∣i−j∣\)。你需要输出这个最小值。
\(1\le n\le 10^6,\sum_i R_i=\sum_i C_i\)

赛时猜结论猜对了,然后看题解的证明被教练捕捉,被定性为赛时抄题解。

结论 \(ans=\sum_{i=1}^n|\sum_{j=1}^iR_j-C_j|\)

可以看做第 \(i\) 堆石子有 \(R_i\) 个,每次可以将一颗石子向左/右移,使得每堆个数变为 \(C_i\),问最小操作次数。

然后就是这样了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6 + 5;
int n;
ll a[N], b[N], ans;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1; i <= n; ++i)
        cin >> b[i];
    for (int i = 1; i <= n; ++i)
        a[i] += a[i - 1], b[i] += b[i - 1], ans += abs(a[i] - b[i]);
    cout << ans << '\n';
    return 0;
}

T3

假设存在 \(k\) 种不同的括号 \((i_1, \cdots, i_k)\),定义:

  • 若序列 \(S_1, S_2\) 为第一类括号序列,则 \(V_i, (i, S_1)\)\(S_1S_2\) 均为第一类括号序列。
  • 若序列 \(S_1, S_2\) 为第二类括号序列,则 \(V_i, (i, S_1)\)\(S_1S_2\) 均为第二类括号序列。
  • 空串既是第一类括号序列又是第二类括号序列。
    简单来说,第一类括号序列只允许匹配时左括号在左边,右括号在右边,而第二类括号序列允许相反顺序的括号匹配。你的任务是,给定正整数 \(n, k\),求出有 \(k\) 个不同括号之时,长度恰好为 \(2n\) 的第一类括号序列个数和第二类括号序列的个数。
    \(998244353\) 取模。
    \(1\le n\le 10^6\)

第一类显然是卡特兰数乘上 \(k^n\)

第二问如果直接乘上 \((2k)^n\) 会发现是错的,原因是像 ()() 这种既可以 \(1\leftrightarrow 2,3\leftrightarrow 4\) 对应,也可以 \(1\leftrightarrow 4,2\leftrightarrow 3\) 匹配。也就是拼接型字符串会出问题。

那先不考虑拼接,算 \(f_{i,j}\) 表示目前有 \(i\) 个括号,栈中还有 \(j\) 个括号未匹配,强制无拼接(不可拆分)的方案数,也就是 \(f_{i,0}\) 不可以向后转移。初始 \(f_{1,1}=2k\)\(f_{i,j}=(2k-1)f_{i-1,j-1}+f_{i-1,j+1}\)\(2k-1\) 是因为需要未匹配括号数增加,所以不能和上一个括号形成匹配。

然后 \(g_i\) 是有 \(i\) 对括号的方案数,有 \(g_i=\sum_{i=0}^{i-1}g_{j}f_{i-j}\)

\(g\) 数组的转移形式就是一个分治 FFT,只要我们能快速求 \(f\) 就可以算出答案。

打表发现 \(f_{i}\) 是卡特兰数乘上一个 \(2k(2k-1)^{(i-1)}\) 的形式。其实其本质网格图就是从 \(0,0\) 走向 \((n,2n)\),中途不能碰到 \(y=x-1\)(大概)。然后枚举上一次碰到 \(y=x-1\) 的位置将方案减掉。\(2k\) 是初始,\(2k-1\) 是中途每个位置的方案。

做完了?

赛后100pts代码
#include <bits/stdc++.h>
using namespace std;
const int N = 4e6 + 5, P = 998244353;

int T, n, k;
int r[N], fac[N], inv[N];
int qp(int x, int y) {
    int z = 1;
    for (; y; y >>= 1, x = 1ll * x * x % P)
        z = 1ll * z * ((y & 1) ? x : 1) % P;
    return z;
}
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
void init() {
    const int n = 2e6;
    fac[0] = inv[0] = 1;
    for (int i = 1; i <= n; ++i)
        fac[i] = 1ll * fac[i - 1] * i % P;
    inv[n] = qp(fac[n], P - 2);
    for (int i = n - 1; i >= 1; --i)
        inv[i] = 1ll * inv[i + 1] * (i + 1) % P;
}
int C(int n, int m) {
    if (n < 0 || m < 0 || m > n)
        return 0;
    return 1ll * fac[n] * inv[n - m] % P * inv[m] % P;
}
int Cat(int n) { return 1ll * C(n + n, n) * qp(n + 1, P - 2) % P; }
void FFT(int *a, int L, int type) {
    for (int i = 0; i < L; ++i) {
        r[i] = (i & 1) * (L >> 1) + (r[i >> 1] >> 1);
        if (r[i] < i)
            swap(a[i], a[r[i]]);
    }
    for (int d = 2; d <= L; d <<= 1) {
        int wn = qp(3, (P - 1) / d), k = d >> 1;
        for (int i = 0; i < L; i += d) {
            for (int j = 0, w = 1; j < k; ++j, w = 1ll * w * wn % P) {
                int tmp = 1ll * a[i + j + k] * w % P;
                a[i + j + k] = (a[i + j] + P - tmp) % P;
                a[i + j] = (a[i + j] + tmp) % P;
            }
        }
    }
    if (type == -1) {
        reverse(a + 1, a + L);
        int inv = qp(L, P - 2);
        for (int i = 0; i < L; ++i)
            a[i] = 1ll * a[i] * inv % P;
    }
}
int tmp[N], tem[N];
void Inv(int n, int *a, int *b) {
    int m = 1;
    b[0] = qp(a[0], P - 2);
    while (m < n) {
        m <<= 1;
        for (int i = 0; i < m; ++i)
            tmp[i] = a[i], tem[i] = b[i];
        FFT(tmp, m << 1, 1), FFT(tem, m << 1, 1);
        for (int i = 0; i < (m << 1); ++i)
            b[i] = 1ll * tem[i] * (2 + P - 1ll * tem[i] * tmp[i] % P) % P;
        FFT(b, m << 1, -1);
        for (int i = m; i < (m << 1); ++i)
            b[i] = 0;
    }
    for (int i = n; i < m; ++i)
        b[i] = 0;
}
int f[N], g[N];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> T >> n >> k, init();
    if (T == 1)
        return cout << 1ll * Cat(n) * qp(k, n) % P << '\n', 0;
    g[0] = 1;
    int s = k * 2;
    for (int i = 0; i < n; ++i)
        g[i + 1] = 1ll * s * (P + C(i * 2, i - 1) - C(i * 2, i)) % P, s = 1ll * s * (k * 2 - 1) % P;
    Inv(n + 1, g, f);
    cout << f[n] << '\n';
    return 0;
}

T4

有一个 \(n\) 个节点构成的完全图,第 \(i\) 个点和第 \(j\) 个点连接边权为 \(\text{lcm}(i,j)\) 的边。你需要求出这个完全图的最大生成树的边权和对 \(10^9+7\) 取模的结果。
\(1\le n\le 10^9\)

结论是 \(x\) 与最大的与 \(x\) 互质的数连边,枚举优化加上 \(O(1)\)\(\gcd\) 在赛时可以过 \(10^7\)

考虑 \(\le n\) 最大的质数 \(p\) 不会离 \(n\) 过远,反正肯定有 \(n-p+1\le 100\),也就是所有数连的边肯定集中在后 \(100\) 数之内。

\(n-p+1\) 个数之间直接单独考虑,前 \(p-1\) 个数考虑容斥。

一开始让所有数连向 \(n\),从大到小枚举 $i\in[p,n] $,考虑每次将连向 \(i\) 且与 \(i\) 不互质的数连向 \(i-1\)(直到 \(p\)这样的数就会变为 \(0\) 个),也就是与 \(i,i+1,\dots,n\) 都不互质的数的和。

现在就是求有与 \(i,i+1,\dots n\) 都不互质的数的和。这里竟然可以直接使用记忆化搜索。枚举与 \(x\)\(\gcd\) 中的素因子有哪些,根据个数求系数为 \(1\)\(-1\),现在就是 \(a_i|x,a_{i+1}|x,\dots,a_n|x\),也就是 \(\text{lcm}(a_i,a_{i+1},\dots,a_n)|x\),然后按层 dfs,记录层数以及目前 \(\text{lcm}\),考虑到 \(\text{lcm}\) 取值有限且很容易超过 \(p-1\),直接判断上界并记忆化,就可以直接通过

赛后100pts代码
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 105, P = 1e9 + 7;
int n, m, ans;
vector<int> a, b[N];
unordered_map<int, int> mp[N];
bool isp(int x) {
    for (int i = 2; i * i <= x; ++i)
        if (x % i == 0)
            return 0;
    return 1;
}
void get(int x) {
    a.clear();
    for (int i = 2; i * i <= x; ++i) {
        if (x % i == 0)
            a.eb(i);
        while (x % i == 0)
            x /= i;
    }
    x > 1 ? a.eb(x) : void();
}
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
int dfs(int i, int s) {
    if (i > n)
        return 1ll * ((m - 1) / s + 1) * ((m - 1) / s) / 2 % P * s % P;
    if (mp[n - i].count(s))
        return mp[n - i][s];
    int res = 0;
    for (int v : b[n - i]) {
        int x = abs(v), y = (v > 0 ? 1 : P - 1);
        long long S = 1ll * x * s / __gcd(x, s);
        if (S < m)
            add(res, 1ll * dfs(i + 1, S) * y % P);
    }
    return mp[n - i][s] = res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n, m = n;
    while (!isp(m))
        --m;
    ans = 1ll * m * (m - 1) / 2 % P * n % P;
    for (int i = n; i >= m; --i) {
        for (int j = n; j > i; --j)
            if (__gcd(i, j) == 1) {
                add(ans, 1ll * i * j % P);
                break;
            }
        get(i);
        for (int s = 1; s < (1 << a.size()); ++s) {
            int x = 1, y = 0;
            for (int j = 0; j < a.size(); ++j)
                x = x * ((s >> j & 1) ? a[j] : 1), y += (s >> j & 1);
            b[n - i].eb(x * ((y & 1) ? 1 : -1));
        }
        add(ans, P - dfs(i, 1));
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2025-08-24 18:08  zzy0618  阅读(11)  评论(0)    收藏  举报