2025.8.20模拟赛

T1

给定一个长度为 \(n\) 由小写字符组成的串,你会将这个串进行若干次修改,你想要求得最少修改次数使得整个串可以出现 noip 作为子串。
修改定义为以下四种操作之一:
1、在任意位置插入一个任意小写字符。
2、删除任意位置的任意一个字符。
3、修改任意位置的一个字符,使其变为另一个字符。
4、交换相邻两个字符。
\(1\le n\le 10^5\)

力大砖飞!

最多 \(4\) 次操作,\(0,3,4\) 次,是好判断的,然后 \(1,2\) 必然是出现了某种子串,这种子串数量应该不大,于是直接 bfs 出一张表来。如果出现了表中的某个字串,那么最多操作这么多次。

bfs 代码
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mk make_pair
using namespace std;
string s = "noip";
queue<pair<string, int>> q;
map<string, int> mp;
void ins(string s, int o) {
    if (!mp.count(s))
        mp[s] = 1, q.push(mk(s, o));
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    q.push(mk(s, 0));
    mp[s] = 1;
    while (q.size()) {
        pair<string, int> p = q.front();
        q.pop();
        s = p.first;
        int t = p.second;
        if (t > 2)
            return 0;
        if (t == 2)
            cout << s << '\n';
        for (int i = 0; i < (int)s.size(); ++i) {
            char c = '?';
            string C = "?";
            swap(s[i], c), ins(s, t + 1), swap(s[i], c);
            s.insert(i, "?"), ins(s, t + 1), s.erase(i, 1);
            C = s[i], s.erase(i, 1), ins(s, t + 1), s.insert(i, C);
        }
        int q = s.size();
        s.insert(q, "?"), ins(s, t + 1), s.erase(q, 1);
        for (int i = 0; i + 1 < (int)s.size(); ++i) {
            swap(s[i], s[i + 1]), ins(s, t + 1), swap(s[i], s[i + 1]);
        }
    }
    return 0;
}

然后从 \(0,1,2,3,4\) 依次判即可,每次判断直接匹配。

赛时代码
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
string t1[16] = {"?oip", "?noip", "oip", "n?ip", "n?oip", "nip", "no?p", "no?ip", "nop", "noi?", "noi?p", "noi", "noip?", "onip", "niop", "nopi",};
string t2[98] = {"??oip", "??ip", "?ip", "?o?p", "?o?ip", "?op", "?oi?", "?oi?p", "?oi", "?oip?", "o?ip", "?iop", "?opi", "??noip", "?n?ip", "?n?oip", "?nip", "?no?p", "?no?ip", "?nop", "?noi?", "?noi?p", "?noi", "?noip?", "?onip", "?niop", "?nopi", "ip", "o?p", "op", "oi?", "oi?p", "oi", "oip?", "iop", "opi", "n??ip", "n??p", "n?p", "n?i?", "n?i?p", "n?i", "n?ip?", "ni?p", "n?pi", "n??oip", "n?o?p", "n?o?ip", "n?op", "n?oi?", "n?oi?p", "n?oi", "n?oip?", "n?iop", "n?opi", "np", "ni?", "ni", "nip?", "inp", "npi", "no??p", "no??", "no?", "no?p?", "on?p", "nop?", "no??ip", "no?i?", "no?i?p", "no?i", "no?ip?", "on?ip", "no?pi", "no", "onp", "npo", "noi??", "oni?", "nio?", "noi??p", "noi?p?", "oni?p", "nio?p", "oni", "nio", "noip??", "onip?", "niop?", "nopi?", "o?nip", "oinp", "onpi", "ni?op", "inop", "nipo", "nop?i", "npoi"};
int T, n;
char s[N];
bool f0() {
    for (int i = 1; i + 3 <= n; ++i)
        if (s[i] == 'n' && s[i + 1] == 'o' && s[i + 2] == 'i' && s[i + 3] == 'p')
            return 1;
    return 0;
}
bool f1() {
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < 16; ++j) {
            int x = t1[j].size();
            if (i + x - 1 <= n) {
                bool fl = 1;
                for (int k = 0; k < x; ++k) {
                    if (s[i + k] != t1[j][k] && t1[j][k] != '?')
                        fl = 0;
                }
                if (fl == 1) return 1;
            }
        }
    }
    return 0;
}
bool f2() {
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < 98; ++j) {
            int x = t2[j].size();
            if (i + x - 1 <= n) {
                bool fl = 1;
                for (int k = 0; k < x; ++k) {
                    if (s[i + k] != t2[j][k] && t2[j][k] != '?') {
                        fl = 0;
                        break;
                    }
                }
                if (fl == 1) return 1;
            }
        }
    }
    return 0;
}
bool f3() {
    for (int i = 1; i <= n; ++i)
        if (s[i] == 'n' || s[i] == 'o' || s[i] == 'i' || s[i] == 'p')
            return 1;
    return 0;
}
void solve() {
    cin >> s + 1, n = strlen(s + 1);
    if (f0()) cout << 0 << '\n';
    else if (f1()) cout << 1 << '\n';
    else if (f2()) cout << 2 << '\n';
    else if (f3()) cout << 3 << '\n';
    else cout << 4 << '\n';
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    for (cin >> T; T; T--)
        solve();
    return 0;
}

T2

给定一个 \(n\times m\) 的网格,你初始在 \(1,1\) 点(左上角),有 \(k\) 个障碍格子。
然后你可以执行以下移动,一次移动定义为从当前所在个点出发,向右侧移动若干格子,或者向下侧移动若干格子。
要求移动不能穿过障碍格子,且你当前至多进行两次移动操作。
问:有多少个网格中的格子可到达。
\(1\le n,m\le 10^5\)

会先沿着第一列/行走,然后再转一个方向

设走到第 \(1\)\(i\) 列,最远向下走 \(f_i\) 的位置;走到第 \(1\) 列第 \(i\) 行,最远向右走 \(g_i\) 的位置,则每个障碍物会使 $ f_x\gets\min(f_x,y-1),g_y\gets\min(g_y,x-1)$。

如果第一行或第一列被某些东西挡住,需将后面的 \(f,g\) 设为 \(0\)

然后答案为 \(\sum_i f_i+\sum_i g_i-sum\)\(sum\)\(f,g\) 重合部分。

考虑每一对 \((x,y)\)\(f_x\ge y,g_y\ge x\) 则他们会重复算一次,那么这就是一个二维偏序,很容易统计。

赛时代码
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int, int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 5;
int n, m, k;
int f[N], g[N], c[N];
pii a[N], b[N];
ll ans;
void add(int x) {
    while (x < N)
        ++c[x], x += x & -x;
}
int ask(int x, int k = 0) {
    while (x)
        k += c[x], x -= x & -x;
    return k;
}
bool cmp1(pii x, pii y) { return x.fi < y.fi; }
bool cmp2(pii x, pii y) { return x.se < y.se; }
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i)
        f[i] = m;
    for (int i = 1; i <= m; ++i)
        g[i] = n;
    for (int t = 1, x, y; t <= k; ++t)
        cin >> x >> y, f[x] = min(f[x], y - 1), g[y] = min(g[y], x - 1);
    for (int i = 1; i <= g[1]; ++i)
        ans += f[i], a[i] = mk(f[i], i);
    for (int i = g[1] + 1; i <= n; ++i)
        f[i] = 0;
    for (int i = 1; i <= f[1]; ++i)
        ans += g[i], b[i] = mk(g[i], i);
    for (int i = f[1] + 1; i <= m; ++i)
        g[i] = 0;
    sort(a + 1, a + g[1] + 1, cmp1);
    sort(b + 1, b + f[1] + 1, cmp2);
    for (int i = 1, j = 1; i <= g[1]; ++i) {
        while (j <= f[1] && a[i].fi >= b[j].se)
            add(b[j].fi), ++j;
        ans -= ask(n) - ask(a[i].se - 1);
    }
    cout << ans << '\n';
    return 0;
}

T3

求有多少个 \(n\) 位十进制数,可以有前导 \(0\),各个数位数字之和 \(\le k\)(即 \(114514\) 拆成 \(1+1+4+5+1+4\) 等),且 \(n\)\(p\) 的倍数。对于所有 \(k\in [0,m]\) 求解。
60pts:\(m\le 50\)
100pts:\(1\le n\le 10^9,1\le p\le 50,1\le m\le 1000\)

\(p\) 的倍数即 \(\sum_{i=0}^{n-1}a_i10^{i}\bmod p\),等价于 \(\sum_{i=0}^{n-1}a_i 10^{i\bmod \varphi(p) +\varphi(p)}\) 然后按 \(\bmod \varphi(p)\) 分组,每组相当于我们有 \(m\) 个箱子,我们有 \(n\) 个球要丢进去,且每个箱子丢的球数 \(\le 9\),由于箱子数量很大,直接用矩阵做是 60pts。最终我们合并结果 \(f_{i,j}\) 表示目前 \(\bmod p =i\),各数位总和为 \(j\) 的方案数,直接做计数即可。

计算这个组合数难点在于 \(\le 9\),考虑钦定有 \(k\) 个位置的球放置个数 \(>9\),剩余随便放即插板计算,最终结果为:

\[\sum_{k=0}^{\min(m, \lfloor n/10 \rfloor)} (-1)^k \binom{m}{k} \binom{m + n - 10k - 1}{m - 1} \]

将右边的组合数用 \(C^m_n=C^{m-n}_m\) 得:

\[\sum_{k=0}^{\min(m, \lfloor n/10 \rfloor)} (-1)^k \binom{m}{k} \binom{m + n - 10k - 1}{n-10k} \]

这样组合数计算复杂度就是可观的了。

赛时代码
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N = 1003, M = 51, P = 998244353;
int n, p, h, m, mx;
int f[M][N], g[M][N], pw[N], inv[N];
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
int phi(int x) {
    int z = 0;
    for (int i = 1; i <= x; ++i)
        if (__gcd(x, i) == 1)
            ++z;
    return z;
}
int qp(int x, int y) {
    int res = 1;
    for (; y; y >>= 1, x = 1ll * x * x % P)
        if (y & 1)
            res = 1ll * x * res % P;
    return res;
}
int C(int n, int m) {
    if (m > n || n < 0 || m < 0)
        return 0;
    m = min(m, n - m);
    mx = max(mx, m);
    int z = 1;
    for (int i = 1; i <= m; ++i)
        z = 1ll * z * (n - i + 1) % P * inv[i] % P;
    return z;
}
map<pair<int, int>, int> mp;
int get(int n, int m) {
    if (mp[make_pair(n, m)])
        return mp[make_pair(n, m)];
    int res = 0;
    for (int k = 0; k <= min(n / 10, m); ++k)
        add(res, 1ll * ((k & 1) ? P - 1 : 1) * C(m, k) % P * C(n + m - 10 * k - 1, m - 1) % P);
    return mp[make_pair(n, m)] = res;
}
int res[N];
void solve(int n, int v) {
    for (int i = 0; i <= m; ++i)
        res[i] = get(i, n);
    for (int i = 0; i < p; ++i)
        for (int j = 0; j <= m; ++j)
            for (int k = 0; j + k <= m; ++k)
                add(g[(i + v * k) % p][j + k], 1ll * f[i][j] * res[k] % P);
    for (int i = 0; i < p; ++i)
        for (int j = 0; j <= m; ++j)
            f[i][j] = g[i][j], g[i][j] = 0;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> p >> m, h = phi(p);
    for (int i = 1; i <= 1000; ++i)
        inv[i] = qp(i, P - 2);
    for (int i = pw[0] = 1; i <= p * 2; ++i)
        pw[i] = pw[i - 1] * 10 % p;
    f[0][0] = 1;
    for (int i = 0; i < h && i < n; ++i)
        solve(1, pw[i]);
    for (int i = h; i < h + h && i < n; ++i)
        solve((n - i - 1) / h + 1, pw[i]);
    for (int i = 1; i <= m; ++i)
        add(f[0][i], f[0][i - 1]);
    for (int i = 0; i <= m; ++i)
        cout << f[0][i] << ' ';
    return 0;
}

T4

具体的有 \(n\times m\) 个机器,当前排布形如一个 \(n\times m\) 的矩形,具体的我们称从北往南数第 \(i\) 行,西往东数第 \(j\) 列的机器成为 \(mc_{i,j}\)
你收到了一串长度为 \(N\) 的神秘代码,这份代码由 E S W N 组成(东南西北),在每一个机子内按照相同的速率重复:1 单位时间显示一个字符,然后切换下一个字符。
比如当前给得串为 SW,则所有机子按照 SWSWSWSW... 一直循环下去,注意,一直循环,即可以存在取出来连续段形如 WSWSW
若当前某些机子激活了,则通过某些联系,另外一些机子也会激活,具体的,对于每台机子有定参数 \(d_i,j\)
若当前参数为 \(0\) 则表示这台机子坏掉了,在任意时刻不能启动。
若当前参数大于 \(0\) ,且存在一个长度不小于 \(d_{i,j}\) 的连续段,满足这段代码被匹配上,则这台机子也被激活。
称一段代码被匹配上,当且仅当这一段代码中对应方向的机子被激活,对当前机子产生影响。
比如,\(N\) 的实际意义是 \(mc_{i,j}\) 被北方的 \(mc_{i−1,j}\) 产生影响,即若 \(mc_{i−1,j}\) 被激活,且 \(mc_{i,j}\) 未被激活,代码 \(N\)\(mc_{i,j}\) 而言称被匹配上。
你会初始选择一个机子让他接受特定频率(注意不能是坏掉的机子),然后开始运转,可以发现选择一台机子启动之后,会导致一系列机子启动。
你想要使得最少的机子启动,请回答在选择某台机子启动的情况下最少会使得多少机子启动,并在最优化最终激活机子数量的情况下求出有多少种选择初始机子的方案。
\(1≤N≤10^5,1≤n,m≤800,d_{i,j}\le 10^5\)

对于每个位置上下左右的情况都可以用一个 \([0,2^4)\) 的二进制集合表示,预处理对于所有 \([0,2^4)\) 情况在 \(N\) 中最长序列,然后就可以 \(O(1)\) 判断当前状态下某位置是否会被激活。

暴力对每个位置直接 bfs,复杂度 \(O(n^2m^2)\) 在赛时有 20,结合特殊性质共有 50pts。

一个性质,若 \(mc_{i,j}\) 可以激活 \(mc_{a,b}\) 那么选 \(mc_{a,b}\) 为起点一定不劣与 \(mc_{i,j}\)

初始每个点作为一个单独的块,对于每个块保留一个点作为关键点,表示块内所有点为起点都能激活这个关键点。然后不断合并这些块,找到那个最不劣的点。若从联通块 \(A\) 的关键点出发,能到达 \(B\) 中的某个点,那么将 \(A\) 合并进 \(B\)

这就有点像 boruvka 生成树了,每次我们对所有块跑一遍,若可以合并则合并,不能合并则将这个联通块扔掉并计算答案,这样复杂度可以达到 \(O(nm\log (nm))\)

对于第二问,我们现在剩下了一堆联通块,之间互相不可达。从每个关键点 \(x\) 做 bfs,对于每个它能到达的点 \(y\)\(y\) 必能到 \(x\),所以这个连通块的答案就是 \(x\) 的可达点数。然后将所有联通块的答案取 \(\min\) 加和即可。

赛后代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3, M = 803, K = M * M;
const int bx[] = {0, 1, 0, -1}, by[] = {1, 0, -1, 0};
char S[N];
int k, n, m, f[16], s[N + N];
int fa[K], ga[K], q[K];
int a[M][M], v[M][M], id[M][M];
int l, r, ans1 = 1e9, ans2;
map<char, int> mp;
bool ck(int x, int y) {
    if (v[x][y] || !a[x][y])
        return 0;
    int res = 0;
    for (int i = 0; i < 4; ++i)
        res |= v[x + bx[i]][y + by[i]] << i;
    return f[res] >= a[x][y];
}
int find(int x) { return ga[x] == x ? x : ga[x] = find(ga[x]); }
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> k >> n >> m >> S + 1;
    mp['E'] = 0, mp['S'] = 1, mp['W'] = 2, mp['N'] = 3;
    for (int i = 1; i <= k; ++i)
        s[i] = s[i + k] = mp[S[i]];
    for (int i = 1; i < 16; ++i) {
        for (int j = 1, t = 0; j <= k + k; ++j)
            if (i >> s[j] & 1)
                f[i] = max(f[i], ++t);
            else
                t = 0;
        if (f[i] > k)
            f[i] = N;
    }
    k = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            cin >> a[i][j], fa[id[i][j] = ++k] = k;
    fa[k + 1] = k + 1;
    while (1) {
        bool brk = 1;
        for (int i = 1; i <= k + 1; ++i)
            ga[i] = fa[i];
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                int I = id[i][j];
                if (fa[I] == I && a[i][j]) {
                    q[v[i][j] = l = r = 1] = i * M + j;
                    int J = -1;
                    while (l <= r) {
                        int x = q[l] / M, y = q[l] % M;
                        ++l;
                        for (int e = 0; e < 4; ++e) {
                            int nx = x + bx[e], ny = y + by[e];
                            if (ck(nx, ny)) {
                                if (fa[id[nx][ny]] != I) {
                                    J = fa[id[nx][ny]];
                                    break;
                                }
                                q[++r] = nx * M + ny, v[nx][ny] = 1;
                            }
                        }
                        if (J != -1)
                            break;
                    }
                    if (~J)
                        brk = 0, ga[I] = find(J);
                    else {
                        if (r < ans1)
                            ans1 = ans2 = r;
                        else if (r == ans1)
                            ans2 += r;
                        ga[I] = k + 1;
                    }
                    for (int e = 0; e <= r; ++e)
                        v[q[e] / M][q[e] % M] = 0;
                }
            }
        }
        if (brk)
            break;
        else {
            for (int i = 1; i <= k + 1; ++i)
                fa[i] = find(i);
        }
    }
    cout << ans1 << '\n' << ans2 << '\n';
    return 0;
}
posted @ 2025-08-20 11:30  zzy0618  阅读(27)  评论(0)    收藏  举报