G - Ban Permutation Editorial by Nachia
箱根駅伝 DP で解釈します。すなわち、
\(\text{dp}[ m ][ k ] = \) ( \(i \leq m\) かつ \(P _ i \leq m\) を満たすような要素のみ考える。考えるべき要素が \(k\) 個であるような場合の数)
となるような DP テーブルをもとに考えます。
\(m\) を増やす遷移に必要な追加情報は、直前 \(X-1\) 個の値が(添え字/順列の要素)に使われた個数のみです。またその情報も含めて遷移させるためには、それら \(X-1\) 個の使われ方 \(4^{X-1}\) 通りの情報があれば十分です。
計算量は \(O(N^2 4^X)\) です。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <atcoder/modint>
using namespace std;
#define rep(i,n) for(int i=0; i<(int)(n); i++)
using Modint = atcoder::static_modint<998244353>;
int main(){
int N, X; cin >> N >> X;
X--; // <-------------------------- 注意
auto table = [&](int n, int x){
return vector(n+1, vector(1<<x, vector(1<<x, Modint())));
};
// popcount
int cnt[32] = {};
rep(d,5) rep(i,32) if(i&(1<<d)) cnt[i]++;
const int mask = X ? 1 << (X-1) : 0;
auto dp = table(0, X);
// 添え字と値をマッチングしていく(箱根駅伝 DP)
// [空きの個数][添え字の直前の空き][値の直前の空き]
dp[0][0][0] = 1;
rep(i,N){
auto tmp = table(i+1, X);
int Q = 1 << X;
rep(n,i+1){
rep(x,Q) rep(y,Q){
auto v = dp[n][x][y];
if(v.val() == 0) continue;
// lx , ly : (添え字 , 値)それぞれの、マッチングできる個数
int lx = n - cnt[x], ly = n - cnt[y];
tmp[n+1][(x>>1)|mask][(y>>1)|mask] += v;
tmp[n][(x>>1)|mask][(y>>1)] += lx * v;
tmp[n][(x>>1)][(y>>1)|mask] += ly * v;
if(n) tmp[n-1][(x>>1)][(y>>1)] += lx * ly * v;
}
}
swap(dp, tmp);
}
auto ans = dp[0][0][0];
cout << ans.val() << '\n';
return 0;
}
posted:
last update: