居酒屋からのエクストリーム参加。D 問題が面白そうだったので突っ込んだ。解けてよかった!!!
問題概要
から までの整数からなる 2 つの順列 に対して、新たな順列 を以下のように定める:
- := 項目の値は である
今、順列の列を
で定める。正整数 が与えられるので を求めよ。
制約
考えたこと
順列を操作していくような問題では
- 逆順列も考えてみる
- 1 つの要素に着目してそれがどう動いて行くかを観察する
- 順列を写像だと思ってどうなるか
あたりが典型考察だと思う。今回は逆順列を考えても何も見えないし、1 つの要素に着目してみてもよくわからなかった。しかし写像だと思うとかなり視界が開けて来る。
まず の定義から とおくと
なことがわかるが、これはつまり であり、
であることを表している!!!!!
ここまでわかると、 を順々に と で表せることがわかる。そして表せば何かが見えて来るのではないかという気持ちになる。
書いてみる
何か見えるだろうか。。。
解説では周期 6 の構造を見出している。僕のは解答と少し違うかもしれないが、周期 3 の以下のような構造を見出した。
- (項が 個)
のように 個ごとに規則性のある合成関数を定義したときに、
が成立する。この予想が立ったならば証明は難しくなくて、数学的帰納法で証明できる。なお証明では
といった関係を用いる。ここまでわかれば、一般に はダブリングを用いて で計算することができる。全体を通して で計算できる。
#include <iostream> #include <vector> using namespace std; // 代表的関数 vector<int> idv, p, q, pi, qi; // pi, qi は p, q の逆写像, idv は恒等写像 // 逆写像計算 vector<int> inv(const vector<int> &v) { int N = v.size(); vector<int> res(N); for (int i = 0; i < N; ++i) res[v[i]] = i; return res; } // 合成写像計算 inline vector<int> operator +(const vector<int> &a, const vector<int> &b) { int N = a.size(); vector<int> res(N); for (int i = 0; i < N; ++i) res[i] = a[b[i]]; return res; } // r_k vector<vector<int> > beki; // beki[i] := r[4×2^i], 前計算しておく vector<int> calc(long long k) { int num = k / 4; int r = k % 4; vector<int> amari = idv; if (r == 0) amari = idv; else if (r == 1) amari = pi; else if (r == 2) amari = pi + qi; else if (r == 3) amari = pi + qi + p; auto syuki = idv; for (int i = 0; i < 35; ++i) if (num & (1LL<<i)) syuki = syuki + beki[i]; return syuki + amari; } // 解く vector<int> solve(long long K) { if (K == 1) return p; if (K == 2) return q; if (K == 3) return q + pi; if (K == 4) return q + pi + qi; if (K == 5) return q + pi + qi + p + qi; if (K == 6) return q + pi + qi + p + p + qi; long long a, b; long long k = K / 3; if (K % 3 == 0) a = k*2-1, b = k*2-3; else if (K % 3 == 1) a = k*2, b = k*2-3; else a = k*2, b = k*2-1; auto res = q + calc(a) + inv(calc(b)) + qi; return res; } int main() { // 入力, 恒等写像, 逆写像 int N, K; cin >> N >> K; idv.resize(N); p.resize(N); q.resize(N); for (int i = 0; i < N; ++i) idv[i] = i; for (int i = 0; i < N; ++i) cin >> p[i], --p[i]; for (int i = 0; i < N; ++i) cin >> q[i], --q[i]; pi = inv(p); qi = inv(q); // 繰り返し構造のダブリング前処理 auto pqpq = pi + qi + p + q; beki.resize(35); beki[0] = pqpq; for (int i = 0; i+1 < 35; ++i) beki[i+1] = beki[i] + beki[i]; // 解く auto res = solve(K); for (auto v : res) cout << v + 1 << " "; cout << endl; }