yukicoder contest 369 H
解き方
※ ここに書く解き方は、解説に書かれている解き方と異なる 二項定理を利用するため、この記事においては$ 0^{0} = 1 とする。
この問題では、$ f(P)^{3} の総和を求めているが、より一般に、正整数$ L に対して$ f(P)^{L} の総和を求める問題を考える。
結論をまず述べると、これは次のように求めることができる。
次のような漸化式を満たす関数$ \mathrm{dp} を考えると、求める答えは$ \mathrm{dp}(N, L) となる。
任意の非負整数$ j について$ \mathrm{dp}(1,j) = 1
任意の正整数$ i と任意の非負整数$ j について$ \mathrm{dp}(i+1, j) = i\times\mathrm{dp}(i, j) + \sum_{k = 0}^{j} {}_{j} \mathrm{C}_{k} \times \mathrm{dp}(i, k)
事前に二項係数を計算しておけば、この漸化式をそのまま実装することで$ \mathrm{dp}(N, L) を$ O(NL^{2}) の計算量で計算できる。
今回の問題では、$ N \le 10^{7} かつ$ L=3 であるため、この方法で実行時間制限内に計算できる。
この方法の正当性を証明するため、下記の主張を帰納的に証明する。
任意の正整数$ i と任意の非負整数$ j に対して$ \mathrm{dp}(i, j) は、$ (1, 2, \ldots, i) の順列$ P 全てに対して$ f(P)^{j} の総和である。
まず$ i = 1 のときを考える。
長さが$ 1 の順列$ P は1通りしかなく、$ f(P) = 1 を満たすので、任意の非負整数$ j について上の主張が成り立つ。
次に$ i = n のときに、上の主張が成り立つとする。また、ある非負整数$ j を考える。
長さが$ n である順列$ n! 個を適当な順番(例えば辞書順)で並べて、そのうち$ l 番目の順列を$ P_{l} とする。また、$ x_{l} = f(P_{l}) とする。
このとき、帰納法の仮定より、任意の非負整数$ k について、$ \mathrm{dp}(n, k) = \sum_{l = 1}^{n!} x_{l}^{k} が成り立つ。
長さが$ (n+1) の順列のうち、$ (n+1) 番目の要素が$ (n+1) である順列$ P を考える。
これは、$ (n+1) 番目の要素を除くことで、長さ$ n の順列と1対1に対応させることができる。
また、このとき$ f(P) の値は、上で1対1に対応させた長さ$ n の順列のそれより$ 1 だけ増える。
よって、$ (n+1) 番目の要素が$ (n+1) であるような長さ$ (n+1) の順列$ P 全てに対して$ f(P)^{j} の総和をとると下のようになる。
$ \sum_{l = 1}^{n!} (x_{l}+1)^{j} = \sum_{l=1}^{n!} \sum_{k=0}^{j} \left( {}_{j} \mathrm{C}_{k} \times x_{l}^{k} \right) = \sum_{k = 0}^{j} \left( {}_{j} \mathrm{C}_{k} \times \left( \sum_{l=1}^{n!} x_{l}^{k} \right) \right) = \sum_{k=0}^{j} {}_{j} \mathrm{C}_{k} \times \mathrm{dp}(n, k)
次に、$ m を$ (n+1) 未満の正整数として、長さが$ (n+1) の順列のうち、$ (n+1) 番目の要素が$ m である順列$ P を考える。
これは、$ (n+1) 番目の要素を除いて、$ m 以上の要素の値を$ 1 減らすことで、長さ$ n の順列と1対1に対応させることができる。
また、このとき$ f(P) の値は、上で1対1に対応させた長さ$ n の順列のそれと等しい。
よって、このような$ P 全てに対して$ f(P)^{j} の総和をとると$ \sum_{l=0}^{n!} x_{l}^{j} = \mathrm{dp}(n,j) となる。
$ (n+1) 未満の正整数$ m のとり方は$ n 通りあるため、これまでに求めた全ての場合を足すと下のようになる。
$ n\times\mathrm{dp}(n,j) + \sum_{k=0}^{j} {}_{j} \mathrm{C}_{k} \times \mathrm{dp}(n, k) = \mathrm{dp}(n+1, j)
以上より、$ i = n+1 のとき、非負整数$ j について主張が成り立つことが確認された。
非負整数$ j の選び方は任意だったため、これは任意の非負整数$ j について$ i = n+1 の場合の主張が成り立つことを意味する。
よって、帰納法により、任意の正整数$ i と任意の非負整数$ j について、上の主張が成り立つ。
解答例
下は上記の方法で解いたときの提出結果である。また、上記の提出の際に提出したソースコードをその下に転記する。
code: C
int main () {
int n = 0;
int res = 0;
long long mod_num = 998244353LL;
long long ans1 = 1LL;
long long ans2 = 1LL;
long long ans3 = 1LL;
long long fact = 1LL;
res = scanf("%d", &n);
for (int i = 2; i <= n; i++) {
long long tmp1 = ans1+fact;
long long tmp2 = ans2+2LL*ans1+fact;
long long tmp3 = ans3+3LL*ans2+3LL*ans1+fact;
ans1 *= (long long) (i-1);
ans2 *= (long long) (i-1);
ans3 *= (long long) (i-1);
ans1 += tmp1;
ans2 += tmp2;
ans3 += tmp3;
ans1 %= mod_num;
ans2 %= mod_num;
ans3 %= mod_num;
fact *= (long long)i;
fact %= mod_num;
}
printf("%lld\n", ans3%mod_num);
return 0;
}
私の提出一覧
table: submissions_yukicoder_contest_369_H
提出のURL 提出時刻 結果 備考
感想
解けないのにF問題やG問題について悩んで時間を使ってしまったので、この問題を解くのに使った時間は20分間もなかったかな。
ABC277Gを似たような方法を解いたことがあったこともあり、上で書いたような方法はおぼろげにはすぐに思いついたので、 時間内に解けそうだったかな(結局はミスがあったから時間内には解けなかったけれど)
しかし、解説をみたら、やはり全然違うことを書いてあるように思えて、解けたと言って良いかは微妙(実はまだ解説を理解できていない)
時間内に解けなかったのは、ループ回数の階乗を表すつもりの変数を、ループ内で全く更新していなくて階乗になっていなかったから。
終了ギリギリに気づいて直して提出しようとしたけれど、ギリギリだったので焦りすぎて、剰余を取り忘れたまま提出してしまった。
提出とほぼ同時に気がついて…
実際に解いたときは、上で定義した関数$ \mathrm{dp} と階乗は別のものとして考えていた。
この記事を書く中で、$ \mathrm{dp} の二番目の引数が$ 0 になる場合も許せば、階乗も$ \mathrm{dp} の中に組み込めることが分かった。
ようやく解説で何をしているのかは理解できた。正しいのはわかったが、よく思いつくなあ。 係数の漸化式を使って多項式を因数分解するのも気づける気がしないし、指数の3乗と係数を掛けたものがほしいから、微分して$ x かけるのを3回繰り返せば全部係数に持ってこれるとか、確かに言われればそうなんやけど