题意相当于计数满足如下条件的长度为 的排列

把条件转换一下,就是对于每个 ,要么 (自己匹配自己),要么存在 满足 (即找一个别的位置匹配);考虑 比较小的时候暴力怎么做,因为 特别小,所以考虑状压 DP:由于 ,所以运用 NOI2023 桂花树 的方法,假设当前考虑到了第 位,用一个集合 表示第 位的状态( 表示该位已经匹配, 表示待匹配), 表示这种状态下的答案,最终答案即为

有如下几种转移(下文中 表示二进制下的按位左移,其他符号类似):

  • 自己匹配自己;
  • :将 设为未匹配;
  • :需要满足 的第 位为 ,用 匹配前面的一个未匹配的

特判一下 的情况,即必须把最早的那一位匹配掉,否则不满足第二个条件。

这个 DP 的过程可以矩阵快速幂优化;对于每个 预处理出转移矩阵的二的非负整数次方,查询时直接暴力做。

参考实现代码(C++17):

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef vector<vector<int> > matrix;
const int p=998244353;
inline void chadd(int &x,int y){
  if((x+=y)>=p)x-=p;
}
inline matrix operator *(matrix a,matrix b){
  matrix c(a.size(),vector<int>(b[0].size()));
  for(int i=0;i<a.size();i++)
    for(int j=0;j<b[0].size();j++)
      for(int k=0;k<a[0].size();k++)
        chadd(c[i][j],1ll*a[i][k]*b[k][j]%p);
  return c;
} // 矩阵乘法
signed main(){
  ios::sync_with_stdio(false);
  vector f(8,vector<matrix>(63));
  for(int k=0;k<8;k++){
    f[k][0].resize(1<<k,vector<int>(1<<k));
    for(int S=0;S<1<<k;S++)
      if(S>>k-1&1)f[k][0][S][S<<1&(1<<k)-1]=1;
      else{
        f[k][0][S][S<<1]=f[k][0][S][S<<1|1]=1;
        for(int j=0;j<k-1;j++)
          if(S>>j&1)f[k][0][S][(S^1<<j)<<1]=1;
      } // 处理若干种转移
    for(int i=1;i<63;i++)
      f[k][i]=f[k][i-1]*f[k][i-1];
  } // 预处理转移矩阵
  int t; cin>>t;
  while(t--){
    int n,k; cin>>n>>k;
    matrix s(1,vector<int>(1<<k)); s[0][0]=1;
    for(int i=0;i<63;i++)
      if(n>>i&1)s=s*f[k][i];
    cout<<s[0][0]<<'\n';
  } // 回答询问
  return 0;
}