2025.8.21模拟赛

T1

给定 \(n\) 个整数 \(a_i\), 中间用加减号隔开. 你可以在里面任意添加括号, 求能得到的最大结果是多少.
\(1\le n\le 10^5\)

如果套三层括号,那么一定可以消成两层及以下的括号。

然后只有在减号的情况下加左括号是有意义的,所以减号时可以加左或右括号,加号就只能加右括号(并且这样会少好多讨论)。

赛时代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, a[N], f[N][3];
char c[N];
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] >> c[i + 1];
    cin >> a[n];
    for (int i = 1; i <= n; ++i)
        f[i][0] = f[i][1] = f[i][2] = -2e18;
    f[1][0] = a[1];
    for (int i = 2; i <= n; ++i) {
        if (c[i] == '-') {
            f[i][0] = max(f[i - 1][0] - a[i], f[i - 1][1] + a[i]);
            f[i][1] = max(f[i - 1][0] - a[i], max(f[i - 1][1] + a[i], f[i - 1][2] + a[i]));
            f[i][2] = max(f[i - 1][1] + a[i], f[i - 1][2] - a[i]);
        } else {
            f[i][0] = max(f[i - 1][0] + a[i], f[i - 1][1] + a[i]);
            f[i][1] = max(f[i - 1][1] - a[i], f[i - 1][2] + a[i]);
            f[i][2] = f[i - 1][2] + a[i];
        }
    }
    cout << f[n][0] << '\n';
    return 0;
}

T2

给定一个 n 个点 m 条边的无向图 G,求 G 上 1 到其他点的最短路的长度。
假设一条路径上的边权依次为 \(w1,w_2,\dots,w_k\) ,于是定义 \(S(w)=\min w_i−\max w_i +\sum w_i\) 为路径长度,求在这个定义下的最短路。
如果不联通,请输出1000000000000000000。
\(1\le n,m\le 2\times 10^5\)

等价的转化是在一条路径上删去掉一条边权,加上一条边权,求最短路。在每个点放 \(4\) 个状态存是否加上,是否去掉的最短路。

如果去掉的不是最大值,那么最终答案一定不优。最小值同理。因此在求最优解的情况下是正确的。

赛时代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
int n, m, head[N], tot;
ll dis[N][4];
bool vis[N][4];
struct node {
    ll dis;
    int id, t;
};
bool operator<(node x, node y) { return x.dis > y.dis; }
priority_queue<node> q;
struct edge {
    int v, w, nxt;
} e[N << 1];
void add(int u, int v, int w) {
    e[++tot] = (edge){v, w, head[u]}, head[u] = tot;
    e[++tot] = (edge){u, w, head[v]}, head[v] = tot;
}
void dij() {
    for (int i = 1; i <= n; ++i)
        dis[i][0] = dis[i][1] = dis[i][2] = dis[i][3] = 1e18;
    dis[1][0] = 0;
    q.push((node){0, 1, 0});
    while (q.size()) {
        int u = q.top().id, t = q.top().t;
        q.pop();
        if (vis[u][t])
            continue;
        vis[u][t] = 1;
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v, w = e[i].w;
            if (dis[u][t] + w < dis[v][t])
                q.push((node){dis[v][t] = dis[u][t] + w, v, t});
            if (!(t & 1) && dis[u][t] + w + w < dis[v][t | 1])
                q.push((node){dis[v][t | 1] = dis[u][t] + w + w, v, (t | 1)});
            if (!(t & 2) && dis[u][t] < dis[v][t | 2])
                q.push((node){dis[v][t | 2] = dis[u][t], v, (t | 2)});
            if (t == 0 && dis[u][t] + w < dis[v][3])
                q.push((node){dis[v][3] = dis[u][t] + w, v, 3});
        }
    }
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1, u, v, w; i <= m; ++i)
        cin >> u >> v >> w, add(u, v, w);
    dij();
    for (int i = 2; i <= n; ++i)
        cout << dis[i][3] << ' ';
    return 0;
}

T3

\(n\) 个人,要分成若干组. 对于第 \(i\) 个人,它所在的组的人数不能超过 \(a_i\),求分组的方案数.组与组之间是不可区分的,每组的人也是无序的,但人与人之间是可以区分的。
50pts: \(1\le n\le 300\)
100pts:\(1\le n\le 3000\)

考虑先按 \(a_i\) 从大到小排序。\(f_{i,j}\) 表示前 \(i\) 个人中,有 \(j\) 个人已经分组的方案数。计算每组贡献时将其挂在最后一个人上,既方便直接用最后一个人的 \(a_i\),又不会算重。

即考虑第 \(i\) 个人是否为最后一个人,以及他所在组的大小,然后算一个组合数,做一个类似背包的东西。

50pts 代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
const int N = 305, P = 998244353;
int n, a[N];
int c[N][N], f[N], g[N];
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
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];
    sort(a + 1, a + n + 1);
    reverse(a + 1, a + n + 1);
    for (int i = c[0][0] = 1; i <= n; ++i)
        for (int j = c[i][0] = 1; j <= i; ++j)
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
    f[0] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= i; ++j) {
            g[j] = f[j];
            for (int k = 1; k <= a[i] && k <= j; ++k)
                add(g[j], 1ll * f[j - k] * c[i - j + k - 1][k - 1] % P);
        }
        for (int j = 0; j <= i; ++j)
            f[j] = g[j], g[j] = 0;
    }
    cout << f[n] << '\n';
    return 0;
}

考虑一次计算多一点的东西。可以从大到小枚举组的大小,将每个人挂在组的大小上计算。首先将 \(a_i\) 等于组的大小的加进来,枚举一层背包,在枚举一层组数,这样总复杂度是 \(O(n\sum_i \frac{n}{i})\)\(O(n^2\log n)\) 的。

一个问题就是组合系数在赛时困扰了我一会儿。

相当于求在 \(n\) 个人中选 \(k\) 组,每组 \(i\) 人。

第一组有 \(\binom{n}{i}\) 种,第二组有 \(\binom{n-i}{i}\) 种。。。然后还要将先后顺序去掉,所以除以一个 \(k!\)

所以是 \(\prod_{l=1}^k\frac {\binom{n-i(l-1)}{i}}{l}\)。直接递推是简单的。

赛时代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3005, P = 998244353;
int n, a[N], inv[N];
int c[N][N], f[N], g[N];
int qp(int x, int y) {
    int z = 1;
    for (; y; y >>= 1, x = 1ll * x * x % P)
        if (y & 1)
            z = 1ll * z * x % P;
    return z;
}
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    inv[0] = 0;
    for (int i = 1, x; i <= n; ++i)
        cin >> x, ++a[x], inv[i] = qp(i, P - 2);
    for (int i = c[0][0] = 1; i <= n; ++i)
        for (int j = c[i][0] = 1; j <= i; ++j)
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
    f[0] = 1;
    for (int i = n, t = 0; i >= 1; --i) {
        for (int j = 0; j <= t; ++j) {
            add(g[j], f[j]);
            int l = t - j + a[i], C = 1;
            for (int k = 1; k * i <= l; ++k) {
                C = 1ll * C * c[l - (k - 1) * i][i] % P * inv[k] % P;
                add(g[i * k + j], 1ll * f[j] * C % P);
            }
        }
        t += a[i];
        for (int j = 0; j <= t; ++j)
            f[j] = g[j], g[j] = 0;
    }
    cout << f[n] << '\n';
    return 0;
}

T4

原题

有一个 \(nm\) 个点的有向分层图, 共有 \(n\) 层, 每层 \(m\) 个点, 每条边一定是从第 \(i\) 层连向第 \(i+1\) 层。
定义 \(f(i,j)\) 表示选择若干条路径, 每条路径从第 \(i\) 层出发, 在第 \(j\) 层结束, 且每条路径在顶点和边上都不交的情况下, 最多选择的路径数.
\(\sum_{i=1}^{n-1}\sum_{j=i+1}^nf(i,j)\)
50pts:\(n1\le n\le 1000\)
100pts:\(1\le n\le 4\times 10^4,1\le m\le 9\)

对于单个求解可以使用网络流。对于每个点拆成进入和走出两个点,中间连一个容量为 \(1\) 的边,其它边容量为 \(1\) 不变。

然后肯定不能真的用网络流去做,考虑最大流等于最小割,而对于这种图一定是割拆点形成的边最优。

所以考虑枚举起点,\(f_{i,s}\) 表示第 \(i\) 层的点集合 \(s\) 的点可以到达起点。

从大到小枚举二进制数 \(s\),再枚举删某个点,复杂度 \(O(n^22^mm)\),赛时就只做到这了。

赛时50pts代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
const int N = 1003, M = 10, K = 1 << 9;
int n, m;
int a[N][M], f[N][K], ans;
void Min(int &x, int y) { x = min(x, y); }
char ch;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1, x; i < n; ++i) {
        for (int j = 0; j < m; ++j)
            for (int k = 0; k < m; ++k)
                cin >> ch, a[i][j] |= ((ch - '0') << k);
    }
    for (int i = 1; i < n; ++i) {
        for (int j = i; j <= n; ++j)
            for (int s = 0; s < (1 << m); ++s)
                f[j][s] = 1e9;
        f[i][(1 << m) - 1] = 0;
        for (int j = i; j <= n; ++j) {
            for (int s = (1 << m) - 1; s >= 0; --s) {
                if (f[j][s] == 1e9)
                    continue;
                for (int k = 0; k < m; ++k)
                    if ((s >> k) & 1)
                        Min(f[j][s ^ (1 << k)], f[j][s] + 1);
            }
            if (j > i)
                ans += f[j][0];
            if (j < n) {
                for (int s = 0; s < (1 << m); ++s) {
                    if (f[j][s] == 1e9)
                        continue;
                    int t = 0;
                    for (int k = 0; k < m; ++k)
                        if ((s >> k) & 1)
                            t |= a[j][k];
                    Min(f[j + 1][t], f[j][s]);
                }
            }
        }
    }
    cout << ans << '\n';
    return 0;
}

考虑对于同一起点,越往后割起来越容易

然后考虑 \(m\) 只有 \(9\),答案只有 \(9\) 种,考虑 \(f_{i,s,j}\) 表示在第 \(i\) 个位置可达状态为 \(s\),花费 \(>j\) 的位置最远在哪。会发现这玩意可以倒着推,且 \(\sum_{j=0}^m max(f_{i,2^m-1,j}-i,0)\) 就是以 \(i\) 为开头的总答案。

转移的时候一种从后面转移来,一种从子集转移来,复杂度 \(O(nm^22^m)\)

赛后100pts代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
const int N = 40003, M = 10, K = 1 << 9;
char ch;
int n, m;
int a[N][K], f[N][K][M];
long long ans;
void Min(int &x, int y) { x = min(x, y); }
int popc(int x, int res = 0) {
    while (x)
        ++res, x -= x & -x;
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1, x; i < n; ++i) {
        for (int j = 0; j < m; ++j)
            for (int k = 0; k < m; ++k)
                cin >> ch, a[i][1 << j] |= ((ch - '0') << k);
        for (int s = 1; s < (1 << m); ++s)
            a[i][s] = a[i][s & -s] | a[i][s ^ (s & -s)];
    }
    memset(f, 0x3f, sizeof(f));
    for (int s = 0; s < (1 << m); ++s)
        for (int j = 0; j <= m; ++j)
            f[n][s][j] = n - (popc(s) <= j);
    for (int i = n - 1; i >= 1; --i) {
        for (int j = 0; j <= m; ++j)
            f[i][0][j] = i - 1;
        for (int s = 0; s < (1 << m); ++s)
            for (int j = 0; j <= m; ++j) {
                Min(f[i][s][j], f[i + 1][a[i][s]][j]);
                if (j == m)
                    continue;
                for (int t = s; t; t ^= (t & -t))
                    Min(f[i][s][j + 1], f[i][s ^ (t & -t)][j]);
            }
        for (int j = 0; j <= m; ++j)
            ans += max(0, f[i][(1 << m) - 1][j] - i), cout << f[i][(1 << m) - 1][j] << ' ';
        cout << '\n';
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2025-08-21 11:29  zzy0618  阅读(15)  评论(0)    收藏  举报