目录

2026牛客暑期多校3-F Not Aqre 2(状压DP,矩阵加速)

2026牛客暑期多校3-F Not Aqre 2(状压DP,矩阵加速)

F. Not Aqre 2

给定一个 n×m 的网格。你需要在每个格子中填入 、、0、1、2 中的一个整数。

如果任意两个共享一条边的格子填有不同的整数,则称该填法为好的。

输出好的填法数对 998244353 取模的结果。

每个测试文件仅包含一组测试数据。

唯一一行包含两个整数 n 和 m ,(1≤n<10,1≤m<998244353)。

输出一个整数表示答案。

2 2
18

在样例中,网格有 2 行 2 列。 第一列有 6 种选法:01,02,10,12,21,20。对于第一列的任意一种选法,第二列恰好有 3 种合法的选法。因此答案为6·3=18

这道题是本场题目最短的,所以也是我开的第一道题。但这道题对我来讲还是太有难度了。想到了 dp 想到了矩阵加速,但是想不出来具体该怎么做。

题意很好理解,给定矩阵行列数,元素只能为 、、1、2、3 其中之一,要求上下左右相邻的元素不同,求有多少种填法。

这道题并不是直接的排列计数,对于单独的一列或一行很容易知道是一个排列计数,但因为是多行多列,其他的行有多少种合法状态需要由已有的行确定。

于是很容易就想到了动态规划。注意到 n 的值域很小,一列最多 3×28 种状态。可以记录每种状态当前有多少合法序列,每种合法序列可以与多少种不同的合法序列相邻,从而线性递推求解。但很明显 m 的值过大,这种方法不行。

对于线性时间复杂度的 dp,很容易想到的是利用矩阵乘法做线性变换,利用矩阵快速幂加速转换矩阵的变换,也就是矩阵加速(多帅的算法啊!)。但直接构建转换矩阵 T 的话,大小可以达到 (3×28)×(3×28),因为每种状态都需要矩阵元素来记录,Tij=1 表示状态 i 可以与 状态 j 相邻。这显然不行,矩阵快速幂时间复杂度为 O(S3logm)。

我们的下一步当然是考虑优化算法了。可以注意到的一点是,我们没有必要记录每一个位置的具体数字是多少,我们关心的只有它们相同还是不同。对于当前位置 $i$,它要么是 $i-2$ 的数字,要么是 $i-1$ 和 $i-2$ 都不是的数字,也就是,相对于前两个位置,它只有两种情况。当最开始的两个位置确定,则对应的最多有 $2^7=128$ 种状态,太强了,居然压缩了这么多!

接下来我们不就可以用矩阵加速了吗。对于一个确定的前两个数字对应的 $128$ 种状态,我们计算初始矩阵 $T$,枚举相邻位置的可能的前两个数字,有 $6$ 种情况,每种情况对应 $128$ 个可能的状态,检查每种情况有多少种相邻状态是可行的。我们计算的是一种特殊起始数字对应的转移矩阵,一共有 $6$ 种起始态,我们只关心相对情况,因此每种起始态的答案是相等的,最后乘 $6$ 就可以了。

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int mod = 998244353;
class mat {
    public:
    vector<vector<int>> dat;
    int a,b;
    mat(const int& a_, const int& b_) {
        a = a_;
        b = b_;
        dat.resize(a+1, vector<int>(b+1,0));
    }
    mat operator*(const mat& m) const {
        if (b!=m.a) {
            return *this;
        }
        mat res(a, m.b);
        for (int i=1;i<=a;++i) {
            for (int j=1;j<=m.b;++j) {
                for (int k=1;k<=b;++k) {
                    res.dat[i][j] += dat[i][k]*m.dat[k][j]%mod;
                    res.dat[i][j] = (res.dat[i][j]+mod)%mod;
                }
            }
        }
        return res;
    }
};
mat mqp(mat a, int b) {
    mat res(a.a,a.b);
    for (int i=1;i<=a.a;++i) {
        res.dat[i][i]=1;
    }
    while (b) {
        if (b&1) {
            res=res*a;
        }
        b>>=1;
        a=a*a;
    }
    return res;
}
int qp(int a,int b) {
    int res  =1;
    a%=mod;
    while (b) {
        if (b&1) {
            res=res*a%mod;
        }
        b>>=1;
        a=a*a%mod;
    }
    return res;
}
signed main() {
    int n,m;
    cin>>n>>m;
    if (n==1) {
        cout << 3ll*qp(2,m-1)%mod << "\n";
        return 0;
    }
    const int maxk = 1ll<<(n-2);
    mat T(maxk,maxk);
    auto geta = [&](int a1,int a2, int k)->vector<int> {
        vector<int> res(n+1);
        res[1]=a1;
        res[2]=a2;
        for (int i=3;i<=n;++i) {
            if ((k>>(i-3)&1)) {
                res[i] = 3-res[i-1]-res[i-2];
            }
            else res[i] = res[i-2];
        }
        return res;
    };
    auto check = [&](const int& a, const int& b) -> int {
        int cnt = 0;
        int a1 = 0, a2 = 1;
        vector<int> arr1 = geta(a1, a2, a);
        for (int b1 = 0; b1 < 3; ++b1) {
            if (b1 == arr1[1]) continue;
            for (int b2 = 0; b2 < 3; ++b2) {
                if (b1 == b2 || b2 == arr1[2]) continue;
                vector<int> arr2 = geta(b1, b2, b);
                bool ok = true;
                for (int i = 1; i <= n; ++i) {
                    if (arr1[i] == arr2[i]) {
                        ok = false;
                        break;
                    }
                }
                if (ok) cnt++;
            }
        }
        return cnt;
    };
    for (int i=0;i<maxk;++i) {
        for (int j=0;j<maxk;++j) {
            T.dat[i+1][j+1]+=check(i,j);
        }
    }
    if (m==1) {
        cout << maxk*6ll%mod << "\n";
        return 0;
    }
    T = mqp(T, m-1);
    int res=0;
    for (int i=1;i<=maxk;++i) {
        for (int j=1;j<=maxk;++j) {
            res += T.dat[i][j];
            res %= mod;
        }
    }
    res = res*6ll%mod;
    cout << res << "\n";
    return 0;
}