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: