跳转至

2024牛客暑期多校训练营2

比赛链接

英文题面

官方题解

标程/数据

难度梯度 ECH BIA GDJ F

A Floor Tiles

题意

\(A\)\(B\) 两种地板,给你 \(n*m\) 的一个矩阵,每个格子可以放一块地板,

刚开始 \((x,y)\) 放了一块 \(ch\) 地板,

问你怎么放置,能使得恰好有 \(k\) 根曲线。

解题思路

刚开始我们先全放 \(A\) 或全放 \(B\),我们会发现一定恰好有 \(n+m\) 根曲线。

然后我们可以发现,最外围随便怎么放,都会有 \(n+m\) 根曲线,所以我们能构成的曲线的数量至少为 \(n+m\)

\(k<n+m\) 时,一定无解。

然后我们需要看怎样构造能让曲线变多,可以容易发现,单位圆为最优的构造方式,

然后根据最开始放的地板,假装全放 \(ch\),然后构造单位圆即可,

我们需要注意 \((x,y)\) 必须放 \(ch\),所以我们通过 \(x+y\) 的奇偶性,来确定具体放什么。

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
vector<string> get(int n, int m, int k, int x, int y, char ch) {
    vector ans(n, string(m, ch));
    int p = ((x + y) % 2) ^ (ch - 'A');
    for (int i = 1 ; i < n ; i++) {
        for (int j = 1 ; j < m ; j++) {
            if (k > 0 && (i + j) % 2 == p) {
                k--;
                ans[i - 1][j] = ans[i][j - 1] = 'B';
                ans[i][j] = ans[i - 1][j - 1] = 'A';
            }
        }
    }
    if (k > 0) {
        return {};
    }
    return ans;
}
void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    int x, y;
    char ch;
    cin >> x >> y >> ch;
    x--;
    y--;
    k -= n + m;
    if (k < 0) {
        cout << "No\n";
        return;
    }
    vector<string> ans = get(n, m, k, x, y, ch);
    if (ans.empty()) {
        cout << "No\n";
        return;
    }
    cout << "Yes\n";
    for (auto &x : ans) {
        cout << x << "\n";
    }
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

B MST

题意

\(n\) 个点 \(m\) 条边的带权无向图,保证不存在自环。

\(q\) 次询问,每次询问给出 \(k\) 个点,问这 \(k\) 个点的最小生成树的权值,没有则为 \(-1\)

\(sum(k)\le 10^5\)

解题思路

我们离线查询,首先存下所有的边,和查询的点集。

对每个查询单据建立并查集,这里需要用 map 存储,并在输入的时候初始化。

\(qr_x\) 表示查询中含有 \(x\) 的查询编号。

然后我们对边的权重从小到大进行排序,然后按顺序依次枚举所有边,

\(u,v,w\) 为每次边的两个节点和边的权重,

我们需要取 \(qr_u\)\(qr_v\),如果一个查询同时出现在这两个中,说明这个边在这个查询中。

如果这两个点在这个查询中已经是连通块了,就跳过,否则使他们联通,并累加权值和点的个数。

这里使用单调优化,定两个指针 \(P1,P2\),如果 \(qr_{u,p1}>qr_{v,p2}\) 那么我们需要 \(qr_v\) 更大才可能使得两者相等,所以 \(p2++\),相反则 \(p1++\),相等则如上述说明,这个边在这个查询中。

复杂度证明,首先对边排序是 \(O(m\log m)\)

然后我们考虑让点分配均匀,也就是一共 \(\sqrt(n)\) 次询问,每次询问 \(\sqrt(n)\) 个点。

那么最后枚举所有边的时间复杂度为 \(O(m*\sqrt(n)*\log n)\)

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
unordered_map<int, int> adj[100005];
array<int, 3> edges[100005];
unordered_map<int, int> f[100005];
vector<int> a[100005], qr[100005];
i64 ans[100005];
int cnt[100005];
int find(int idx, int x) {
    return f[idx][x] == x ? x : f[idx][x] = find(idx, f[idx][x]);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1 ; i <= m ; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        if (u > v) {
            swap(u, v);
        }
        if (adj[u].count(v)) {
            w = min(w, adj[u][v]);
        }
        adj[u][v] = w;
    }
    int tot1 = 0;
    for (int u = 1 ; u <= n ; u++) {
        for (auto &[v, w] : adj[u]) {
            edges[tot1++] = {u, v, w}; 
        }
    }
    sort(edges, edges + tot1, [&](const auto &x, const auto &y) {
        return x[2] < y[2];
    });
    for (int i = 0  ; i < tot1 ; i++) {
        auto [u, v, w] = edges[i];
    }
    for (int i = 1 ; i <= q ; i++) {
        int k;
        cin >> k;
        a[i].resize(k);
        for (int j = 0 ; j < k ; j++) {
            int x;
            cin >> x;
            f[i][x] = x;
            qr[x].push_back(i);
        }
    }
    for (int i = 0 ; i < tot1 ; i++) {
        auto [u, v, w] = edges[i];
        int P1 = 0, P2 = 0;
        int n1 = qr[u].size(), n2 = qr[v].size();
        while (P1 < n1 && P2 < n2) {
            int x = qr[u][P1], y = qr[v][P2];
            if (x > y) {
                P2++;
                continue;
            }
            if (x < y) {
                P1++;
                continue;
            }
            if (x == y) {
                int tu = find(x, u);
                int tv = find(y, v);
                if (tu != tv) {
                    f[x][tu] = tv;
                    ans[x] += w;
                    cnt[x]++;
                }
                P1++;
                P2++;
            }
        }
    }
    for (int i = 1 ; i <= q ; i++) {
        if (cnt[i] != (int) a[i].size() - 1) {
            ans[i] = -1;
        }
        cout << ans[i] << "\n";
    }
    return 0;
}

C Red Walking on Grid

题意

有一个 \(2*n\) 的矩阵,分别有字符 'R' 和 'W'。

你可以任意选一个字符 'R' 作为起点,

每次操作,你可以选择一个相邻的字符为 'R' 的格子,然后走到格子上,

当你离开一个有字符 'R' 的格子,这个字符变为 'W',

问你最多可能操作多少次。

解题思路

首先只有两行,假设我们不向左走,

那么我们只可能有四种走法,

① 在第一行的格子直接往右走。

② 在第一行的格子先向下走再向右走。

③ 在第二行的格子直接往右走。

④ 在第二行的格子先向上走再向右走。

定义 \(dp_{i,j]\) 为在第 \(i\) 列第 \(j\) 行时,往上下右方向走过的最大步数。

这样如果我们从第一列开始走,使用动态规划就能推出,起点在第一列的答案。

只要我们每一列都初始化,就可以做到起点任意了。

需要注意到的是,\(dp\) 数组是到达这个点的最大步数,

这个时候我们没有往上下走,

如果此时可以上下走,那么我们答案就会减少,

所以我们递推完之后再 check 一遍加上即可。

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
char mp[1000005][3];
int dp[1000005][3]; //0 上 1 下
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>> n ;
    for(int i = 1 ; i <= 2 ; i++){
        for(int j = 1 ; j <= n ; j++){
            cin >> mp[j][i];
        }
    }
    for (int i = 1 ; i <= n ; i++) {
        if (mp[i][1] == 'R') {
            dp[i][1] = max(dp[i - 1][1], 1);
        }
        if (mp[i][2] == 'R') {
            dp[i][2] = max(dp[i - 1][2], 1);
        }
        { // 上 -> 下右
            if (mp[i - 1][1] == 'R' && mp[i - 1][2] == 'R' && mp[i][2] == 'R') {
                dp[i][2] = max(dp[i][2], dp[i - 1][1] + 2);
            }
        }
        { // 上 -> 右
            if (mp[i - 1][1] == 'R' && mp[i][1] == 'R') {
                dp[i][1] = max(dp[i][1], dp[i - 1][1] + 1);
            }
        }
        { // 下 -> 上右
            if (mp[i - 1][2] == 'R' && mp[i - 1][1] == 'R' && mp[i][1] == 'R') {
                dp[i][1] = max(dp[i][1], dp[i - 1][2] + 2);
            }
        }
        { // 下 -> 右
            if (mp[i - 1][2] == 'R' && mp[i][2] == 'R') {
                dp[i][2] = max(dp[i][2], dp[i - 1][2] + 1);
            }
        }
    }
    int ans = 0;
    for (int i = 1 ; i <= n ; i++) {
        if (mp[i][1] == 'R' && mp[i][2] == 'R') {
            dp[i][1]++;
            dp[i][2]++;
        }
        ans = max(ans, max(dp[i][1], dp[i][2]) - 1);
    }
    cout << ans << "\n";
    return 0;
}

D Taking Candies

题意

\(A,B\) 两人竞拍 \(n\) 盒糖果,第 \(i\) 盒糖果有 \(a_i\) 颗糖

\(n\) 次竞价,每次竞价的过程是:

  • 假设一开始 \(A,B\) 分别有 \(p,q\) 个硬币。
  • \(A\) 先出价 \(0\le v_0\le p\)
  • 然后 \(B\) 出价 \(v_0< v_1\le q\) 或弃权
  • 然后 \(A\) 出价 \(v_1< v_2\le p\) 或弃权
  • 一直到有人弃权,假设最后出价是 \(v_m\),则出价的人将 \(v_m\) 个硬币给对方,并且出价的人可以随意拿走一盒糖果。

一开始 \(A,B\) 分别有 \(x,y\) 个硬币,问最优策略下 \(A\) 最多拿多少糖果。

解题思路

很容易知道,拿糖果的顺序是按照数量递减的(先拿多的)。

然后我们可以发现,他们两人轮流竞价,肯定能存在每人只出一次价格的最优策略。

硬币的总和不变。

设总共有 \(s=x+y\) 枚硬币。

定义 \(f_{i,t}\) 为倒数第 \(i\)\(A\) 取得 \(t\) 个糖果所需的最少硬币,可以知道 \(f_{0,0}=0\)

假设 \(A\)\(x\) 个硬币,\(B\)\(y\) 个硬币,这一轮的糖果数量为 \(w\)

\(A\) 想得到这个糖果,那么他出价的最大值为 \(x-f_{i-1,t-w}\)

那么如果 \(B\) 要拿到这个糖果,要出价 \(x-f_{i-1,t-w}+1\)

如果 \(x+x-f_{i-1,t-w}+1\ge f_{i-1,t}\),说明 \(A\) 之后可以拿到 \(t\) 个糖果,

\(f_{i,t}=\lfloor\dfrac{f_{i-1,t-w},f_{i-1,t}}{2}\rfloor\)

然后我们就得到了转移方程,我们发现糖果的数量很大,不能直接这么写。

然后我们看到 \(f_{i,t}\) 的取值为 \([0,x+y]\),然后我们可以发现 \(f_{i,t}\) 是关于 \(t\) 单调的,

也就是说 \(t\) 相同时, \(f_{i,t}\) 递增。

所以我们可以用 vector 套 pair 存一个区间来维护单调性。

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
const i64 INF = 1e12;
int a[100005];
int main() {
    int n, x, y;
    cin >> n >> x >> y;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    int s = x + y;
    vector<array<i64, 2>> f;
    f.push_back({0, 0}); // 初始状态
    f.push_back({s + 1, INF}); // 无穷状态(不可到达)
    for (int i = 1 ; i <= n ; i++) {
        vector<array<i64, 2>> g;
        auto add = [&](i64 cost, i64 val) {
            if (g.empty() || g.back()[0] < cost) {
                g.push_back({cost, val});
                return;
            }
            g.back()[1] = val;
        };
        int L = 0, R = 0;
        int len = f.size();
        while (L < len - 1 || R < len - 1) {
            int cost = (f[L][0] + f[R][0]) / 2;
            if (f[L][1] < f[R][1] + a[i]) { // 差小于 w, 不可以拿到 w
                add(cost, f[L][1]);
                L++;
            } else { // 差大于等于 w, 可以拿到 w
                add(cost, f[R][1] + a[i]);
                R++;
            }
        }
        g.push_back({s + 1, INF}); // 无穷状态(不可到达)
        f.swap(g);
    }
    i64 ans = 0;
    for (auto &[cost, val] : f) {
        if (cost <= x) {
            ans = val;
        }
    }
    cout << ans;
    return 0;
}

E GCD VS XOR

题意

给出一个正整数 \(x\),和式子 \(\gcd(x,y)=x\oplus y\)\(y\)\(0\le y<x\)

输出一个满足条件的 \(y\)。(不存在输出 \(-1\))

解题思路

看到异或运算,首先就要考虑二进制。

通过相邻的数互质和相邻的数异或为 \(1\),可以写出 \(\gcd(x,x+1)=x\oplus y=1\)

那么我们可以知道 \(\gcd(x*2^i,(x+1)*2^i)=2^i\)

然后可以发现 \((x*2^i)\oplus((x+1)*2^i)=2^i\)

乘上 \(2^i\) 相当于二进制往左移了 \(i\) 位,

所以我们可以找到 \(x\) 二进制为 \(1\) 的最低位将其置 \(0\)

因为 \(1\) 的最低位的低位全是 \(0\)

那么就构造出 \(x*2^i\)\((x+1)*2^i\) 了,

如何找二进制为 \(1\) 的最低位,使用 \(lowbit\) 的原理 \(x\&-x\) 即可解决。

综述,答案为 \(ans=x-(x\&-x)\)

因为 \(ans>0\),需要特判当 \(ans=0\) 时,\(ans=-1\)

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
void solve() {
    i64 x;
    cin >> x;
    x = x & -x;
    if (x == 0) {
        x = -1;
    }
    cout << x << "\n";
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

G The Set of Squares

题意

定义一个多重集是好的,当且仅当,这个多重集内元素的乘积是一个\(x^2\)\(x\in N\),集合的权值为 \(x\)

给你一个多重集,问它的所有子集中所有好集的权值和。答案对 \(1e9+7\) 取模。

解题思路

我们可以知道权值是 \(\le1000\) 的,我们可以发现,只有 \(\le\sqrt{1000}\) 的质数会在一个数中出现多次。

然后我们可以知道 这种质数只有 \({2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}\),一共 \(11\) 个,

我们可以对这些质数状压,即 \(2^{11}\) 种状态。

这里我们想到背包,对于每个 \(>\sqrt{1000}\) 质数进行分组,那么我们可以想到如果当前质数是 \(>\sqrt{1000}\) 的,

那么只有相同组别的会产生贡献。

所以直接状压+分组背包即可解决。时间复杂度 \(o(n*2^{11}*11)\)

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
int a[1005];
int prime[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
const i64 P = 1000000007;
vector<array<i64, 2>> v[1005];
array<i64, 1 << 12> f;
int main() {
    int n;
    cin >> n;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
    }
    for (int i = 1 ; i <= n ; i++) {
        int mask = 0;
        i64 val = 1;
        int x = a[i];
        for (int j = 0 ; j < 11 ; j++) {
            int p = prime[j];
            while (x % p == 0) {
                x /= p;
                if (mask >> j & 1) {
                    val = val * p % P;
                }
                mask ^= 1 << j;
            }
        }
        v[x].push_back({mask, val});
    }
    f[0] = 1;
    for (auto &[mask, val] : v[1]) {
        auto g = f;
        for (int j = 0 ; j < (1 << 11) ; j++) {
            int temp = j ^ mask;
            i64 res = f[j] * val % P;
            int both = j & mask;
            for (int k = 0 ; k < 11 ; k++) {
                if (both >> k & 1) {
                    res = res * prime[k] % P;
                }
            }
            g[temp] = (g[temp] + res) % P;
        }
        f = g;
    }
    for (int i = 2 ; i <= 1000 ; i++) {
        if (i * i <= 1000) {
            continue;
        }
        for (auto &[mask, val] : v[i]) {
            auto g = f;
            mask |= 1 << 11;
            for (int j = 0 ; j < (1 << 12) ; j++) {
                int temp = j ^ mask;
                i64 res = f[j] * val % P;
                int both = j & mask;
                for (int k = 0 ; k < 11 ; k++) {
                    if (both >> k & 1) {
                        res = res * prime[k] % P;
                    }
                }
                if (both >> 11 & 1) {
                    res *= i;
                }
                g[temp] = (g[temp] + res) % P;
            }
            f = g;
        }
        for (int j = 1 << 11 ; j < (1 << 12) ; j++) {
            f[j] = 0;
        }
    }
    cout << (f[0] + P - 1) % P;
    return 0;
}

H Instructions Substring

题意

给出一个仅含有 'W', 'A', 'S', 'D' 的字符串,和一个点 \((x,y)\)

有一个平面直角坐标系,起点为 \((0,0)\)

'W','A','S','D' 分别代表往上,下,左,右走一格。

问按顺序执行操作能经过点 \((x,y)\) 的子串有多少种。

解题思路

首先可以知道,如果目标点为 \((0,0)\)

那么一开始就经过了这个点,所以任意子串都满足要求,

\(ans=\frac{n*(n+1)}{2}\)

然后我们要知道,左端点相同的子串,右端点大的一定经过右端点小的路径中的所有点。

所以我们可以考虑枚举左端点,

如果我们知道了左端点所以我们可以考虑枚举左端点,

当执行一段操作后恰好到达了目标点,那么再往后面加任何操作都是满足要求的。

所以我们可以枚举左端点,对于每个左端点都求出一个满足要求的最小右端点,

通过减法求出对应左端点满足的子串数量即可不重不漏的统计答案。

对于如何快速的找到右端点,我们可以对 \(x\)\(y\) 方向进行后缀和,

\(sum_{X,L}-sum_{X,R-1}=x\)\(sum_{Y,L}-sum_{Y,R-1}=y\)

我们只需要开一个 map 存 \({X,Y}\) 的最小下标就好了。

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
array<int, 2> a[200005];
array<int, 2> sum[200005];
map<array<int, 2>, int> st;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, x, y;
    cin >> n >> x >> y;
    string str;
    cin >> str;
    str = " " + str;
    if (x == 0 && y == 0) {
        cout << 1LL * (1 + n) * n / 2 << "\n";
        return 0;
    }
    for (int i = 1 ; i <= n ; i++) {
        if (str[i] == 'A') {
            a[i][0] = -1;
        }
        if (str[i] == 'D') {
            a[i][0] = 1;
        }
        if (str[i] == 'W') {
            a[i][1] = 1;
        }
        if (str[i] == 'S') {
            a[i][1] = -1;
        }
    }
    st[{0, 0}] = n + 1;
    for (int i = n ; i >= 1 ; i--) {
        sum[i][0] = a[i][0] + sum[i + 1][0];
        sum[i][1] = a[i][1] + sum[i + 1][1];
    }
    i64 ans = 0;
    for (int i = n ; i >= 1 ; i--) {
        int X = sum[i][0] - x;
        int Y = sum[i][1] - y;
        if (st.count({X, Y})) {
            ans += n + 2 - st[{X, Y}];
        }
        int idx = i;
        if (st.count({sum[i][0], sum[i][1]})) {
            idx = min(idx, st[{sum[i][0], sum[i][1]}]);
        }
        st[{sum[i][0], sum[i][1]}] = idx;
    }
    cout << ans << "\n";
    return 0;
}

I Red Playing Cards

题意

\(2n\) 个数,\(1\)\(n\) 各有两张,

你可以进行任意次操作,每次操作你可以选择两张相同的数 \(x\)

假设下标分别为 \(L,R\),把下标在区间 \([L,R]\) 的数都删掉,那么你能获得价值 \(x*(R-L+1)\)

问最大可能获得的价值。

解题思路

我们可以发现,删除的区间一定不能交叉,而且删除的区间前后端点的数一定相同。

那么我们就只要考虑这个区间内的区间,因为可能有嵌套,所以用 dfs 枚举所有区间,

使用 dp 来计算这个区间的最大权值,从而处理出所有区间的贡献。

最后进行一次 dp 递推出最大权值。

解题代码

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int a[6005];
int dp[6005];
int pos[6005][2];
int temp[6005][6005];
int dfs(int x) {
    if (dp[x] != 0) {
        return dp[x];
    }
    int L = pos[x][0], R = pos[x][1];
    temp[x][L] = x;
    for (int i = L + 1 ; i <= R ; i++) {
        temp[x][i] = max(temp[x][i], temp[x][i - 1] + x);
        int QL = pos[a[i]][0], QR = pos[a[i]][1];
        if (i == QL && QR < R) {
            temp[x][QR] = max(temp[x][QR], temp[x][i - 1] + dfs(a[i]));
        }
    }
    return dp[x] = temp[x][R];
}
int ans[6005];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1 ; i <= 2 * n ; i++) {
        cin >> a[i];
        if (pos[a[i]][0]) {
            pos[a[i]][1] = i;
        } else {
            pos[a[i]][0] = i;
        }
    }
    for (int i = 1 ; i <= n ; i++) {
        dfs(i);
    }
    for (int i = 1 ; i <= 2 * n ; i++) {
        ans[i] = max(ans[i], ans[i - 1]);
        int L = pos[a[i]][0], R = pos[a[i]][1];
        if (i == L) {
            ans[R] = max(ans[R], ans[i - 1] + dp[a[i]]);
        }
    }
    cout << ans[2 * n] << "\n";
    return 0;
}

J Involutions

题意

定义 \(involution\) 为集合上有映射 \(f:A\to A\)\(f(f(x))=A\)

定义 \(FP(f)=\mid{x\in A\mid f(x)=x}\mid\)

定义 \(w(f)=a^{FP(f)}\)

\(\{1,2,3,…,n\}\) 上所有 \(involution\) 的权值和,答案对 \(998244353\) 取模。

解题思路

\(g(n)\)\(\{1,2,3,…,n\}\)上所有 \(involution\) 的权值和。

易知: \(g(0)=1\)\(g(1)=a\)

当我们现在有 \(n-1\) 个数,我们新增一个数 \(x\),使 \(f(x)=x\),这样也有 \(f(f(x))=x\),此时 \(FP\) 多了 \(1\),所以贡献了 \(a*g(n-1)\)

当我们现在有 \(n-2\) 个数,我们新增一个数 \(x\),我们可以选择已有的任意一个数 \(y\),使 \(f(x)=y,f(y)=x\),这样也有 \(f(f(x))=x,f(f(y))=y\),此时 \(FP\) 不变,在 \(g(n-2)\) 的基础上,任意选一个数出来和 \(x\) 匹配,所以贡献 \(g(n-2)*(n-1)\)

所以我们得到式子 \(g(n)=a*g(n-1)+(n-1)*g(n-2)\)

这里使用整式递推模版即可。

解题代码

代码
C++
{ 多项式基础模版 }
{ 整式递推模版 }
int main() {
    i64 n, a;
    cin >> n >> a;
    if (n <= 5) {
        vector<int> ans(n + 1);
        ans[0] = 1; ans[1] = a;
        for (int i = 2 ; i <= n ; i++) {
            ans[i] = (a * ans[i - 1] + 1LL * ans[i - 2] * (i - 1)) % P;
        }
        cout << ans[n] << "\n";
        return 0;
    } else {
        recursive::init();
        vector<Int> f{1, a};
        vector<vector<Int>> p{
            {P - 1, 0},
            {a, 0},
            {P - 1, 1}
        };
        cout << recursive::recursive(n, f, p) << "\n";
    }
    return 0;
}