ARC071_F 题解

RMJ 已经炸了好久了,什么时候能修好啊……

题意简述

定义“好”的序列 $\{a_i\}$ 为满足以下两个性质的无穷序列:

  • 所有项都是 $[1,n]$ 中的正整数。
  • 第 $n$ 项及之后的所有项都相等。
  • 对每一个 $a_i$,满足 $a_{i+1}\sim a_{i+a_i}$ 两两之间都相等。

给出 $n$,求“好”的序列的数量。

题目分析

与其他题解差不多,仍是采用 DP。设 $f_i$ 表示第 $i\sim n$ 都填好的方案数。那么由于第 $n$ 位及之后的项可以随便填,故 $f_n=n$;同样的道理,第 $n-1$ 项也可以随便填,故 $f_{n-1}=n^2$。

然后考虑状态转移,分三种情况:

  1. $a_i=1$:方案数直接为 $f_{i+1}$。

  2. $a_i>1,a_{i+1}=1$:那么 $a_{i+1}\sim a_{i+a_i}$ 都确定为 $1$,方案数为 $\displaystyle\sum_{a_i=2}^n f_{i+a_i+1}$。特别地,对 $k>n$,我们令 $f_k=1$,所以原式实质上等于 $\displaystyle i+1+\sum^n_{j=i+3} f_j$。

  3. $a_i,a_{i+1}>1$:则 $a_{i+1}$ 及之后的所有项都必须相同。而 $a_i$ 和 $a_{i+1}$ 可以随便填。方案数为 $(n-1)^2$。

综上,$\displaystyle f_i=i+1+(n-1)^2+f_{i+1}+\sum^n_{j=i+3} f_j$。直接用前缀和维护 $\displaystyle \sum^n_{j=i+3} f_j$ 进行计算可以做到 $O(n)$。

考虑更优的做法。我们发现计算这个递推式其实可以很方便地用矩阵优化。初步设计答案矩阵为 $\begin{bmatrix} f_{i+1} & f_{i+2} & f_{i+3} & i+1 & (n-1)^2 & \displaystyle\sum_{j=i+4}^nf_j & -1 \end{bmatrix}$

为了将其递推到 $\begin{bmatrix} f_i & f_{i+1} & f_{i+2} & i & (n-1)^2 & \displaystyle\sum_{j=i+3}^nf_j & -1 \end{bmatrix}$,设计转移矩阵:

$$\begin{bmatrix} 1&1&0&0&0&0&0\\ 0&0&1&0&0&0&0\\ 1&0&0&0&0&1&0\\ 1&0&0&1&0&0&0\\ 1&0&0&0&1&0&0\\ 1&0&0&0&0&1&0\\ 0&0&0&1&0&0&1 \end{bmatrix}$$

我们一列一列进行解释:

第 $1$ 列,由状态转移方程得来。

第 $2$、$3$ 列,简单的滚动更新。

第 $4$ 列,$i+1+(-1)=i$。第 $5$、$7$ 列保持不变。

第 $6$ 列,$\displaystyle f_{i+3}+\sum_{j=i+4}^nf_j=\sum_{j=i+3}^nf_j$。

这样用矩阵快速幂做确实是可行的,但我们会发现 $7\times 7$ 的矩阵做矩阵乘法一方面会使常数变得很大,另一方面矩阵中的一些项其实是没有必要的。

考虑优化。首先,$\displaystyle f_{i+1}+\sum_{j=i+3}^nf_j$ 其实就等于 $(\displaystyle\sum_{j=i+1}^nf_j)-f_{i+2}$,根本没必要维护 $f_{i+3}$;同时,$(n-1)^2$ 和 $i+1$ 其实可以合并进前缀和里。这样我们就得到了简化后的 $1\times 4$ 的答案矩阵:$\begin{bmatrix} f_{i+1} & f_{i+2} & i+1+(n-1)^2+\displaystyle\sum_{j=i+2}^nf_j & -1 \end{bmatrix}$

相应地改变转移矩阵:

$$\begin{bmatrix} 1&1&1&0\\ -1&0&0&0\\ 1&0&1&0\\ 0&0&1&1 \end{bmatrix}$$

这样程序的常数就会小不少,总时间复杂度为 $O(\log n)$,对本题绰绰有余。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int n;
struct matrix
{
    int n,m;
    int a[5][5];
    void init(int k)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(i!=j)
                    a[i][j]=0;
                else
                    a[i][j]=k;
    }
    friend matrix operator *(matrix a,matrix b)//矩阵乘法 
    {
        matrix c;
        c.n=a.n,c.m=a.m;
        c.init(0);
        for(int i=1;i<=a.n;i++)
            for(int j=1;j<=b.m;j++)
                for(int k=1;k<=a.m;k++)
                    c.a[i][j]=(c.a[i][j]+1ll*a.a[i][k]*b.a[k][j]%mod)%mod;
        return c;
    }
    friend matrix operator ^(matrix a,int b)//矩阵快速幂 
    {
        matrix res;
        res.n=res.m=a.n;
        res.init(1);
        for(;b;b>>=1)
        {
            if(b&1)
                res=res*a;
            a=a*a;
        }
        return res;
    }
}fs,tr;
int main()
{
    scanf("%d",&n);
    if(n==1)
    {
        printf("1");
        return 0;
    }//特判 1 
    fs.n=1,fs.m=tr.n=tr.m=4;
    fs.a[1][1]=1ll*n*n%mod,fs.a[1][2]=n,fs.a[1][3]=1ll*n*n%mod,fs.a[1][4]=mod-1;//初始答案矩阵,注意 -1 是 mod-1 
    tr.init(0);
    tr.a[1][1]=tr.a[1][2]=tr.a[1][3]=tr.a[3][1]=tr.a[3][3]=tr.a[4][3]=tr.a[4][4]=1,tr.a[2][1]=mod-1;//转移矩阵 
    tr=tr^(n-2);//计算转移矩阵的幂 
    fs=fs*tr;//得到最终答案矩阵 
    printf("%d",fs.a[1][1]);
    return 0;
}
posted @ 2023-09-09 15:36  Hadtsti  阅读(7)  评论(1编辑  收藏  举报  来源