P15569 [COCI 2025/2026 #5] 结构 / Struktura 题解

P15569 COCI 2025/2026 #5 结构 / Struktura 题解

tag : 矩阵快速幂 矩阵加速 DP 转移
赛时这道题写了 \(O(n)\) DP,得了 76 分,但不太熟悉矩阵加速,没有写出正解

这道题有更简单的方法,但为了练习矩阵加速 DP,用了如下方法

题意

给定 \(n,k\),独立、等概率地从 \(1\) 到 \(k\) 中取 \(n\) 个数,得到数组 \(a_1,\dots,a_n\)。称 \(a\) 是结构,当且仅当同时满足

  • \(1,2,\dots,n\) 在 \(a\) 中各出现恰好一次,即 \(a\) 是 \(1\) 到 \(n\) 的一个排列;
  • 对每个 \(i\),\(|a_i+i-n-1|\le 1\)。

求随机数组恰为结构的概率,模 \(10^9+7\) 输出,即

\\\frac{\\text{结构个数}}{k\^n}\\pmod{10\^9+7} \\

数据范围 \(n,k\le 10^9\)。

第二条等价于每个位置只有三种候选值

\a_i\\in\\{n-i,\\ n+1-i,\\ n+2-i\\} \\

落出 \(1,n\) 的候选值不合法。另外 \(k<n\) 时答案为 \(0\),因为排列里的 \(n\) 也必须取自 \(1\) 到 \(k\)。


显然,这道题有一个比较简单的暴力 DP 转移

显然,每个 \(a_i\) 的取值只有三种,从 \(i=1\) 到 \(n\) 分别如下:

\(i\) \(1\) \(2\) \(3\) \(\cdots\) \(n-2\) \(n-1\) \(n\)
① \(n+1\) \(n\) \(n-1\) \(\cdots\) \(4\) \(3\) \(2\)
② \(n\) \(n-1\) \(n-2\) \(\cdots\) \(3\) \(2\) \(1\)
③ \(n-1\) \(n-2\) \(n-3\) \(\cdots\) \(2\) \(1\) \(0\)

\(fiab\) : 表示第 \(i\) 位选第 \(a\) 个,第 \(i - 1\) 位选第 \(b\) 个,注意边界 \(1\) 到 \(n\)

cpp 复制代码
// init
f[2][1][1] = 0, f[2][1][3] = 1;
f[2][2][1] = 0, f[2][2][2] = 1;
f[2][3][1] = 0, f[2][3][2] = 1, f[2][3][3] = 1;

for (int i = 2; i < n; i ++) {
    f[i + 1][1][1] = (f[i + 1][1][1] + f[i][1][1]) % MOD;
    f[i + 1][1][3] = (f[i + 1][1][3] + f[i][3][1] + f[i][3][2]) % MOD;
    f[i + 1][2][1] = (f[i + 1][2][1] + f[i][1][1] + f[i][1][3]) % MOD;
    f[i + 1][2][2] = (f[i + 1][2][2] + f[i][2][1] + f[i][2][2]) % MOD;
    f[i + 1][3][1] = (f[i + 1][3][1] + f[i][1][1] + f[i][1][3]) % MOD;
    f[i + 1][3][2] = (f[i + 1][3][2] + f[i][2][1] + f[i][2][2]) % MOD;
    f[i + 1][3][3] = (f[i + 1][3][3] + f[i][3][1] + f[i][3][2] + f[i][3][3]) % MOD;
}

cnt = (cnt + f[n][1][1] + f[n][1][3]) % MOD;
cnt = (cnt + f[n][2][1] + f[n][2][2]) % MOD;

能够用矩阵加速的五个条件(\(AI\)总结)

  • 一步能写成加权和 :新状态里每一项都是旧状态的"常数 × 旧值"再相加,比如 \(f i = f i − 1 + f i − 2 、 b 3 = a 7 + a 8\) 。不能出现两个状态相乘、开平方、绝对值、条件判断。
  • 权重不随步数变 : \(f i = f i − 1 + f i − 2\) 里系数是 1 , 1 ,每步都一样;如果是 \(f i = i ⋅ f i − 1\) 或 \(f i = 2 i f i − 1\) ,系数每步都变,就不能直接用。
  • 状态个数固定 :每一步只需要固定几个数(比如本题 9 个、斐波那契 2 个)。如果需要记住整段历史、或者状态数是 \(2^n\) 这种规模,就不行。
  • 只要"某一时刻的状态" :答案能直接从第 \(n\) 步的状态里取出来。
  • 再加一个现实条件: \(n\) 要足够大 ,否则直接循环反而更快。

所以,显然本题可以用矩阵加速

矩阵加速DP转移:

令 \(id ( a , b ) = ( a − 1 ) ⋅ 3 + b\)

所以可以把暴力 DP 转移写成如下形式 (重要\(! ! !\))

\\\begin{bmatrix} f_{i,1} \& f_{i,2} \& \\cdots \& f_{i,9} \\end{bmatrix} = \\begin{bmatrix} f_{i-1,1} \& f_{i-1,2} \& \\cdots \& f_{i-1,9} \\end{bmatrix} \\begin{bmatrix} \\mathrm{base}_{11} \& \\mathrm{base}_{12} \& \\cdots \& \\mathrm{base}_{19} \\\\ \\mathrm{base}_{21} \& \\mathrm{base}_{22} \& \\cdots \& \\mathrm{base}_{29} \\\\ \\vdots \& \\vdots \& \\ddots \& \\vdots \\\\ \\mathrm{base}_{91} \& \\mathrm{base}_{92} \& \\cdots \& \\mathrm{base}_{99} \\end{bmatrix} \\

其中

\\\begin{aligned} f_{i,1} ={}\& f_{i-1,1}\\cdot \\mathrm{base}_{1,1} + f_{i-1,2}\\cdot \\mathrm{base}_{2,1} + f_{i-1,3}\\cdot \\mathrm{base}_{3,1} + f_{i-1,4}\\cdot \\mathrm{base}_{4,1} \\\\ \&+ f_{i-1,5}\\cdot \\mathrm{base}_{5,1} + f_{i-1,6}\\cdot \\mathrm{base}_{6,1} + f_{i-1,7}\\cdot \\mathrm{base}_{7,1} + f_{i-1,8}\\cdot \\mathrm{base}_{8,1} + f_{i-1,9}\\cdot \\mathrm{base}_{9,1} \\end{aligned}即 \\

考虑如何推出 \(base\) 矩阵

由暴力 DP

\(f_{i,1}=f_{i-1,1}\)

\(f_{i,3}=f_{i-1,7}+f_{i-1,8}\)

\(f_{i,4}=f_{i-1,1}+f_{i-1,3}\)

\(f_{i,5}=f_{i-1,4}+f_{i-1,5}\)

\(f_{i,7}=f_{i-1,1}+f_{i-1,3}\)

\(f_{i,8}=f_{i-1,4}+f_{i-1,5}\)

\(f_{i,9}=f_{i-1,7}+f_{i-1,8}+f_{i-1,9}\)

对于 \(f_{i,1}\) 由\(f_{i,1}=f_{i-1,1}\) 可以得到:

\\\begin{pmatrix} \\mathrm{base}_{1,1}\\\\ \\mathrm{base}_{2,1}\\\\ \\vdots\\\\ \\mathrm{base}_{9,1} \\end{pmatrix} = \\begin{pmatrix} 1\\\\ 0\\\\ \\vdots\\\\ 0 \\end{pmatrix} \\

同理可以得到整个 \(base\) 矩阵

\\\mathrm{base}= \\begin{pmatrix} 1\&0\&0\&1\&0\&0\&1\&0\&0\\\\ 0\&0\&0\&0\&0\&0\&0\&0\&0\\\\ 0\&0\&0\&1\&0\&0\&1\&0\&0\\\\ 0\&0\&0\&0\&1\&0\&0\&1\&0\\\\ 0\&0\&0\&0\&1\&0\&0\&1\&0\\\\ 0\&0\&0\&0\&0\&0\&0\&0\&0\\\\ 0\&0\&1\&0\&0\&0\&0\&0\&1\\\\ 0\&0\&1\&0\&0\&0\&0\&0\&1\\\\ 0\&0\&0\&0\&0\&0\&0\&0\&1 \\end{pmatrix} \\

于是 \(bj = \sum_{i=1}^{9} ai * baseij\)

于是直接开两个数组(无需用矩阵存\(b\), \(a\))

cpp 复制代码
Matrix p = qpow(base, n - 2);

a[3] = a[5] = a[8] = a[9] = 1;
for (int j = 1; j <= 9; j ++) {
    for (int i = 1; i <= 9; i ++) {
        b[j] = (b[j] + a[i] * p.mat[i][j]) % MOD;
    }
}

答案:b[1] + b[3] + b[4] + b[5]


AC code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

const int N = 10;
const ll MOD = 1e9 + 7;

ll nn, k;

ll a[N], b[N];

struct Matrix {
    int n;
    ll mat[N][N];

    Matrix(int _n = 0) {
        n = _n;
        memset(mat, 0, sizeof(mat));
    }

    void E() {
        for (int i = 1; i <= n; i ++) mat[i][i] = 1;
    }
} ;

Matrix mul(Matrix a, Matrix b) {
    int n = a.n;
    Matrix ret(n);

    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= n; j ++) {
            for (int k = 1; k <= n; k ++) {
                ret.mat[i][j] = (ret.mat[i][j] + a.mat[i][k] * b.mat[k][j]) % MOD;
            }
        }
    }

    return ret;
}

Matrix qpow(Matrix a, ll b) {
    int n = a.n;
    Matrix res(n);
    res.E();

    while (b) {
        if (b & 1) res = mul(res, a);
        a = mul(a, a);
        b >>= 1;
    }

    return res;
}

ll q_pow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }

    return res;
}

int main () {
    freopen("struktura.in", "r", stdin);
    freopen("struktura.out", "w", stdout);

    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> nn >> k;

    if (nn == 1) {
        cout << q_pow(k, MOD - 2) << '\n';
        return 0;
    }

    if (k < nn) {
        cout << 0 << '\n';
        return 0;
    }

    Matrix base(9);

    base.mat[1][1] = 
    base.mat[1][4] = base.mat[1][7] =
    base.mat[3][4] = base.mat[3][7] =
    base.mat[4][5] = base.mat[4][8] = 
    base.mat[5][5] = base.mat[5][8] = 
    base.mat[7][3] = base.mat[7][9] = 
    base.mat[8][3] = base.mat[8][9] = 
    base.mat[9][9] = 1;


    Matrix p = qpow(base, nn - 2);

    a[3] = a[5] = a[8] = a[9] = 1;
    for (int j = 1; j <= 9; j ++) {
        for (int i = 1; i <= 9; i ++) {
            b[j] = (b[j] + a[i] * p.mat[i][j]) % MOD;
        }
    }

    ll cnt = (b[1] + b[3] + b[4] + b[5]) % MOD;

    ll tot = q_pow(k, nn) % MOD;
    cout << cnt * q_pow(tot, MOD - 2) % MOD << '\n';

    return 0;
}

注意特判 \(n == 1\)

务必注意矩阵的 \(i, j\),非常容易搞反

相关推荐
镜子AI11 小时前
镜子AI工程实践:用 Python 自动生成 GEO 问答语料与意图覆盖矩阵(零依赖)
人工智能·python·矩阵
学习论之费曼学习法1 天前
无人值守AI内容矩阵实战(8):标题 A/B 实验的 6 个工程实现细节,隔日对比与自动判胜
人工智能·python·算法·矩阵·自动化·a/b测试·内容矩阵
晒太羊的猫1 天前
hot100矩阵问题
线性代数·矩阵
轻口味1 天前
启元无真机二开实战1:54 个接口能做什么、不能做什么——启元 Q1/T1 SDK 能力全景与版本矩阵
线性代数·矩阵·ros2·具身智能·启元机器人
Angel Q.2 天前
协方差矩阵:3DGS 里“椭球的形状说明书“
线性代数·3d·矩阵
lvwangshu3 天前
T3 涂鸦带师 题解
题解·贪心·倍增·技巧·思维好题·断环成链
BSD_HY3 天前
智能家居中控面板的结构暗战:薄膜开关为什么还没退场
矩阵·智能家居·薄膜开关·源头工厂
莫叫石榴姐4 天前
半导体业务数仓建模: Lot、Wafer、Die、Recipe、OEE、YMS 建模要点
数据仓库·sql·面试·职场和发展·矩阵·1024程序员节
Tisfy5 天前
LeetCode 3644.排序排列:不对则与(脑筋急转弯)
数据结构·算法·leetcode·题解·位运算