4281 字
17 分钟
2026牛客暑期多校训练营1
2026-08-03

A. 2090 Virus#

给定 nn 个字符串,问每个字符串是否满足下述条件:

  • 字符串长度恰好为 88
  • 字符串中第 1,3,5,71,3,5,7 个字符是辅音字母。
  • 字符串中第 2,4,6,82,4,6,8 个字符是元音字母。

模拟。

int check(string s, int pos) {
if (s[pos] == 'a' || s[pos] == 'e' || s[pos] == 'i'
|| s[pos] == 'o' || s[pos] == 'u')
return 1;
else
return 0;
}
void solve() {
string s;
cin >> s;
if (s.size() == 8
&& (check(s, 1) && check(s, 3) && check(s, 5) && check(s, 7))
&& !(check(s, 0) || check(s, 2) || check(s, 4) || check(s, 6)))
cout << "Suspected Virus\n";
else
cout << "Well-Being\n";
}

C. Fish Eating#

给定一个 n×mn\times m 的网格,在网格上有一个大鱼吃小鱼的游戏,要求不经过障碍、可以吃不大于自己的鱼、每吃一条鱼大小加 11。初始时所有格子都是障碍,有 qq 个操作,操作有两种:

  • (x,y)(x,y) 处放一条大小为 vv 的鱼,问这条鱼最多吃掉多少鱼。保证每个 vv 都不小于之前的 vv
  • 假设将 (x,y)(x,y) 处的鱼放大,问最少放大多少,能使这条鱼吃掉最多的鱼。

关键在于 “保证每个 vv 都不小于之前的 vv“,这样我们可以使用并查集。对于每个操作 1,显然我们可以将其相邻的所有鱼连同自己加入到一个集合。 这样可以吃到的鱼就是 最终的集合大小1最终的集合大小-1

实际上我认为难点在于处理操作 2,而 mx 数组就是为了实现这个操作。 对于鱼 ind,用 mx[ind] 表示吃掉所在集合全部鱼所需的最小 vv,用 mx[ind] 减去原本的 v[ind]00 取较大值即为答案。那要怎么维护 mx 数组呢?

首先我们要确定,每次加入新的鱼,都需要对他周围鱼的 mx 进行更新。 把新鱼吃掉所需的最小 vmin=vnowsznxt+1v_{min}=v_{now}-sz_{nxt}+1,即新鱼的 vnowv_{now} 减去周围鱼所在集合合体能增加的重量。 而此时周围鱼的 mx 就是 max(mxnow,vmin)max(mx_{now},v_{min})

在通过 merge 合并新鱼之后,更新原本集合的所有 mx 显然是不现实的,所以就像通过 find 传递 f 一样,我们也通过 find 传递 mx。 每次我们把当前的 mx[x] 更新为自己和父亲节点中较大的 mx,通过递归调用,即可更新当前节点到跟节点路径上所有未更新的的 mx。实际上,这类似于一种懒标记,只在需要的时候更新。

int n, m, q;
vector<vector<int>> g;
int dx[] = { 0, 0, -1, 1 }, dy[] = { -1, 1, 0, 0 };
struct DSU {
vector<int> f, sz, mx, v;
DSU() { }
DSU(int n) { init(n); }
void init(int n) {
f.resize(n);
iota(f.begin(), f.end(), 0);
mx.assign(n, 0);
v.assign(n, 0);
sz.assign(n, 1);
}
int find(int x) {
if (f[x] == x)
return x;
int tmp = find(f[x]);
mx[x] = max(mx[x], mx[f[x]]);
return f[x] = tmp;
}
bool merge(int now, int nxt) {
now = find(now);
now = find(nxt);
if (now == nxt)
return false;
f[nxt] = now;
sz[now] += sz[nxt];
mx[nxt] = max(mx[now], v[now] - sz[nxt] + 1);
return true;
}
};
DSU dsu;
signed main() {
cin >> n >> m >> q;
g.resize(n, vector<int>(m, -1));
dsu.init(q);
int x, y, pre = 0, op, val = 0;
for (int i = 0; i < q; i++) {
cin >> op >> x >> y;
x = x ^ pre;
y = y ^ pre;
x--;
y--;
if (op == 1) {
g[x][y] = i;
cin >> val;
dsu.v[i] = val;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx >= 0 && nx < n && ny >= 0 && ny < m
&& g[nx][ny] != -1) {
dsu.merge(i, g[nx][ny]);
}
}
pre = dsu.sz[dsu.find(i)] - 1;
} else {
int ind = g[x][y];
dsu.find(ind);
pre = max(0LL, dsu.mx[ind] - dsu.v[ind]);
}
cout << pre << endl;
}
return 0;
}

E. Permutation Evaluation#

对于一个 00n1n-1 的排列 PP,定义其权值 f(P)=0i<j<n(PjPi)f(P)=\sum_{0\leq i<j<n}(P_{j}-P_{i})。给定一排列,求其权值。

对于任意位置 ii,其对于答案的贡献是 v(i)=ij<n(PjPi)=ij<nPj(n1i)×Piv(i)=\sum_{i\leq j<n}(P_{j}-P_{i})=\sum_{i\leq j<n}P_{j}-(n-1-i)\times P_{i}

signed main() {
int n;
cin >> n;
vector<int> p(n), pre(n);
for (int i = 0; i < n; i++) {
cin >> p[i];
}
pre[n - 1] = p[n - 1];
for (int i = n - 2; i >= 0; i--) {
pre[i] = pre[i + 1] + p[i];
}
int ans = 0;
for (int i = 0; i < n - 1; i++) {
ans += pre[i + 1] - (n - 1 - i) * p[i];
}
cout << ans << endl;
return 0;
}

F. Permutation Generation#

对于一个 00n1n-1 的排列 PP,定义其权值 f(P)=0i<j<n(PjPi)f(P)=\sum_{0\leq i<j<n}(P_{j}-P_{i})。给定一排列和两整数 k,xk,x。 求一排列 PP' 满足 Pk=xP'_k=xf(P)f(P)(modn)f(P)\equiv f(P') \pmod {n}

注意到,对于一排列 PP,循环位移任意位后 f(P)modnf(P) \bmod n 不变。 记录原本 xx 所在位置 pos,循环位移位数即为 (kpos+n)modn(k-pos+n)\bmod n。 模拟循环位移即得答案。

signed main() {
int n, k, x, pos = -1;
cin >> n >> k >> x;
vector<int> p(n);
for (int i = 0; i < n; i++) {
cin >> p[i];
if (p[i] == x)
pos = i;
}
int d = (k - pos + n) % n;
for (int i = 0; i < n; i++) {
cout << p[(i - d + n) % n] << ' ';
}
return 0;
}

G. Precision Error?!#

给定一个整数 nn,需要构建一个点集 SS,其中有不超过 2n+22n+2 个点位于三维欧几里得空间中。 要求对于每个点 PSP\in S 恰好有 nn 个不同的点 QSQ\in S 满足 dis(P,Q)=1dis(P,Q)=1,其中 dis(P,Q)dis(P,Q) 表示两点的欧几里得距离。 允许误差 ϵ=0.01\epsilon=0.01,即满足下面条件视为正确答案:

  • 对于任意两个不同的点 P,QSP,Q \in S,有 dis(P,Q)>ϵdis(P,Q)>\epsilon
  • 对于每个点 PSP\in S,恰好有 nn 个点 QSQ\in S 满足 1ϵ<dis(P,Q)<1+ϵ1-\epsilon<dis(P,Q)<1+\epsilon

依旧注意到这一块。由于 ϵ\epsilon 的存在,假设 dis(P,Q)=1.001,zPzQ=1dis(P,Q)=1.001,|z_{P}-z_{Q}|=1,可得到 max(xPxQ)=0.1002max(|x_{P}-x_{Q}|)=0.1002。 此时相对于 (0,0,0)(0,0,0) 处的点,在 z=1z=1 处恰好有长宽为 0.10.1 的正方形可以放得开 1010 个满足 dis(P,Q)>ϵdis(P,Q)>\epsilon 的点,在正方形中共可以放置 100100 个点。满足题意要求。

const double d=0.0105;
void solve(){
int n;cin>>n;
cout<<2*n<<endl;
for(int i=0;i<n;i++){
double x=(i%10)*d;
double y=(i/10)*d;
cout<<x<<' '<<y<<' '<<0.0<<endl;
}
for(int i=0;i<n;i++){
double x=(i%10)*d;
double y=(i/10)*d;
cout<<x<<' '<<y<<' '<<1.0<<endl;
}
}

H. Rock-Paper-Scissors Master#

Alice 和 Bob 玩一个 kk 轮的石头剪刀布游戏。游戏开始时,两人各有 33 张牌,每张牌是 石头 (R)、剪刀 (S) 或布 (P)。每轮游戏规则如下:

  1. 双方可看到彼此手中的全部 66 张牌。
  2. Alice 先选一张手牌打出。
  3. Bob 在看到 Alice 打出的牌后,选择自己的一张牌打出。
  4. 根据标准规则判断胜负。若 Alice 获胜则得到 33 分,平局则得到 11 分,输得 00 分。
  5. 双方丢弃打出的牌,并独自等概率地获得一张新的石头、剪刀或布。

Alice 希望最大化自己的期望总分,Bob 则希望最小化 Alice 的期望总分。 给定游戏轮数 kk、Alice 和 Bob 的初始手牌,求出双方均采用最优策略时,Alice 的最终得分。

期望 DP。其实在补题的时候对我影响最大的是编码状态。总纠结于 100100 种状态,想要找到一种最优编码,反倒浪费了很多时间,实际上直接用 3 进制也是可以的。下面给出一种 GPT 实现的 100100 种状态的编码方式。

int encode(int r, int s, int p) {//每人 10 种状态,分别作为十位和个位。
int rank = r * 4 - r * (r - 1) / 2;
rank += s;
return rank;
}

这道题解决了编码之后的第二个点,就是要注意到,在足够多轮次之后, 每一轮的期望得分收敛为常数这个事实。 严谨的数学推导我不太擅长,但是在被告知这个事实后,略加思考便可以知道这个结论的正确性。

也就是说,大 kk 对于我们的影响微乎其微,我们只需要计算出在不同初始状态下的小轮次中的期望得分即可。这就是 init() 在做的事。 而对于每轮游戏,我们不能想当然地认为 Bob 一定会获胜,Bob 可能当前手中没有获胜所需的牌,也可能赢了这轮,反倒导致后面全盘皆输,所以我们需要计算所有可能后的期望。 每轮游戏 Alice 和 Bob 各自可能出 33 种牌,可能获得 33 种牌,共 34=813^4=81 种可能,而我们只需要计算较小轮次。 假设计算前 100100 轮,最优编码下,只需要计算 100×100×81=8.1×105100\times 100\times 81=8.1\times 10^5 次。 即使设置共 729729 种状态,也只需要计算 729×100×816×106729\times 100\times 81 \approx 6 \times 10^6 次。

对于每轮游戏期望得分的计算,由于我们枚举了状态,假设开始前状态为 SS',结束后状态为 SS,当前轮次得分为 scorescore。 这里使用开始前状态为 SS' 的原因是,对于状态 SS,我们有不同的方式可以到达。 每种出牌的可能我们需要计算得分,然后再根据得到的牌来确定状态,最后确定期望得分。 而 Bob 希望期望得分最小,Alice 希望期望得分最大。所以 Bob 选择所有期望里最小的,而 Alice 选择 Bob 的选择中最大的。 那么状态转移显然为 dp[i+1][S]=max(min(score+19dp[i][S]))dp[i+1][S]= max\left( min\left( score+ \frac{1}{9}\sum dp[i][S'] \right) \right)

在大轮次游戏中,由于每轮的期望得分收敛,完全可以用一个常数乘以轮数代替,最后得到如下代码。

map<char, int> mp = { { 'R', 0 }, { 'S', 1 }, { 'P', 2 } };
double dp[101][729];
int encode(vector<int> state) {
    int ans = 0;
    for (int i = 5; i >= 0; i--) {
        ans = ans * 3 + state[i];
    }
    return ans;
}
vector<int> decode(int x) {
    vector<int> state(6);
    for (int i = 0; i < 6; i++) {
        state[i] = x % 3;
        x /= 3;
    }
    return state;
}
double win(int a, int b) {
    if ((a == 0 && b == 1) || (a == 1 && b == 2) || (a == 2 && b == 0))
        return 3;
    else if (a == b)
        return 1;
    else
        return 0;
}
void init() {
    for (int i = 1; i < 101; i++) {
        for (int j = 0; j < 729; j++) {
            vector<int> state = decode(j);
            double mx = 0;
            for (int a = 0; a < 3; a++) {
                double mn = 1e18;
                for (int b = 3; b < 6; b++) {
                    double sum = win(state[a], state[b]);
                    for (int nxt1 = 0; nxt1 < 3; nxt1++) {
                        int tmp1 = state[a];
                        state[a] = nxt1;
                        for (int nxt2 = 0; nxt2 < 3; nxt2++) {
                            int tmp2 = state[b];
                            state[b] = nxt2;
                            sum += dp[i - 1][encode(state)] / 9;
                            state[b] = tmp2;
                        }
                        state[a] = tmp1;
                    }
                    mn = min(mn, sum);
                }
                mx = max(mx, mn);
            }
            dp[i][j] = mx;
        }
    }
}
void solve() {
    int k;
    cin >> k;
    string alice, bob;
    cin >> alice >> bob;
    vector<int> state(6);
    for (int i = 0; i < 3; i++) {
        state[i] = mp[alice[i]];
        state[i + 3] = mp[bob[i]];
    }
    if (k <= 100)
        cout << dp[k][encode(state)] << endl;
    else {
        double avg = (dp[100][0] - dp[99][0]);
        cout << dp[100][encode(state)] + avg * (k - 100) << endl;
    }
}
signed main() {
    cout << fixed << setprecision(12);
    init();
    int _t;
    cin >> _t;
    while (_t--)
        solve();
    return 0;
}

J. Show Hand#

模拟德州扑克。。没啥可说的,今年我见过的最极致最纯粹的模拟。

L. Substrings of Substrings#

AC 自动机 · 改。等有空再补题解吧,我要先补题了。。。

using namespace std;
const int MOD = 998244353;
int n, q;
string S;
vector<int> a, pre, minpre, maxsuff, prepre, presuff;
vector<int> length, lastpos, finalans, finalsum;
inline int mmod(int x) {
    x %= MOD;
    return (x < 0 ? (x + MOD) : x);
}
void solve(int id, int l) {
    int len = length[id];
    int r = l + len - 1;
    int mx = maxsuff[r + 1] - minpre[l];
    finalans[id] = max(finalans[id], mx);
    int previous = lastpos[id];
    int countl = l - previous, countr = n - r;
    int lsum = prepre[l];
    if (previous >= 0)
        lsum = mmod(lsum - prepre[previous]);
    int rsum = presuff[r + 1];
    finalsum[id] = mmod(finalsum[id] + mmod(mmod(countl) * rsum - mmod(countr) * lsum));
    lastpos[id] = l;
}
struct AC {
    struct Node {
        int child[26], fail, outputlink;
        vector<int> index;
        Node() {
            memset(child, -1, sizeof(child));
            fail = 0;
            outputlink = 0;
        }
    };
    vector<Node> tree;
    vector<int> length;
    AC(int q) {
        tree.emplace_back();
        length.resize(q);
    }
    void insert(string& s, int id) {
        int u = 0;
        length[id] = s.size();
        for (char x : s) {
            int c = x - 'a';
            if (tree[u].child[c] == -1) {
                tree[u].child[c] = tree.size();
                tree.emplace_back();
            }
            u = tree[u].child[c];
        }
        tree[u].index.push_back(id);
    }
    void build() {
        queue<int> q;
        for (int c = 0; c < 26; c++) {
            int v = tree[0].child[c];
            if (v == -1)
                tree[0].child[c] = 0;
            else {
                tree[v].fail = 0;
                tree[v].outputlink = 0;
                q.push(v);
            }
        }
        while (q.size()) {
            int u = q.front();
            q.pop();
            for (int c = 0; c < 26; c++) {
                int v = tree[u].child[c];
                if (v == -1) {
                    tree[u].child[c] = tree[tree[u].fail].child[c];
                    continue;
                }
                tree[v].fail = tree[tree[u].fail].child[c];
                int f = tree[v].fail;
                if (tree[f].index.size())
                    tree[v].outputlink = f;
                else
                    tree[v].outputlink = tree[f].outputlink;
                q.push(v);
            }
        }
    }
    void query(const string& s) {
        int u = 0;
        for (int r = 0; r < s.size(); r++) {
            int c = s[r] - 'a';
            u = tree[u].child[c];
            for (int id : tree[u].index) {
                int l = r - length[id] + 1;
                solve(id, l);
            }
            for (int v = tree[u].outputlink; v != 0; v = tree[v].outputlink) {
                for (int id : tree[v].index) {
                    int l = r - length[id] + 1;
                    solve(id, l);
                }
            }
        }
    }
};
signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    cin >> n >> q;
    cin >> S;
    a.resize(n), pre.resize(n + 1);
    minpre.resize(n + 1), maxsuff.resize(n + 1);
    prepre.resize(n + 1), presuff.resize(n + 1);
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        pre[i] = pre[i - 1] + a[i - 1];
    for (int i = 1; i <= n; i++)
        minpre[i] = min(minpre[i - 1], pre[i]);
    maxsuff[n] = pre[n];
    for (int i = n - 1; i >= 0; i--)
        maxsuff[i] = max(maxsuff[i + 1], pre[i]);
    for (int i = 1; i <= n; i++)
        prepre[i] = mmod(prepre[i - 1] + pre[i]);
    presuff[n] = mmod(pre[n]);
    for (int i = n - 1; i >= 0; i--)
        presuff[i] = mmod(presuff[i + 1] + pre[i]);
    AC ac(q);
    unordered_map<string, int> mp;
    vector<int> rmp(q);
    int uniquecount = 0;
    for (int i = 0; i < q; i++) {
        string p;
        cin >> p;
        auto it = mp.find(p);
        if (it == mp.end()) {
            mp[p] = uniquecount;
            rmp[i] = uniquecount;
            ac.insert(p, uniquecount);
            uniquecount++;
        } else
            rmp[i] = it->second;
    }
    length = ac.length;
    lastpos.resize(uniquecount, -1);
    finalans.resize(uniquecount, LLONG_MIN);
    finalsum.resize(uniquecount, 0);
    ac.build();
    ac.query(S);
    for (int i = 0; i < q; i++) {
        int id = rmp[i];
        cout << finalans[id] << ' ' << finalsum[id] << endl;
    }
    return 0;
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

2026牛客暑期多校训练营1
https://leaf146.cn/posts/2026牛客暑期多校训练营1
作者
LeAf146
发布于
2026-08-03
许可协议
MIT

部分信息可能已经过时