AOJ 12/4
2014-12-04 | [Competitive programming]2378 (SolveMe)
部屋の集合 S = {0, 1, …, N-1} とし,部屋 i からの右耳での行き先を f(i), 左耳での行き先を g(i) とする. f, g は S -> S の関数である. 条件は f^Z・g・f^Y・g・f^X = id である.よって g は全単射で,X + Y + Z > 0 であれば f も全単射である. (X + Y + Z = 0 の場合は以下の議論は使えないが,f が全単射に限らないことを除いて結果が一致する) 全単射であるから,逆写像を自由にぶつけることができて便利である.
すると,適当な置き換えにより結局 S -> S の全単射 f, g で,f^2 = g^|X+Z-Y| なるものの数を数えればいいことになる. f^2 = g^|X+Z-Y| = h とおく.h を固定したときの f, g の数を数える気分で答えを求める.
S -> S の全単射は,一般に「サイクル」(f(0) = 1, f(1) = 2, f(2) = 0 のように,関数適用で循環するときの循環節) に分解できる. 関数の繰り返し適用により,サイクルがどう分解するかを調べると,長さ l のサイクルは k 回適用のあとで,長さ l/gcd(l, k) のサイクル gcd(l, k) 個に分解することがわかる.
あとは,長さ l のサイクルがちょうど m 個与えられたとき,繰り返し適用の後でそれらに分解するような「サイクルたち」が何個あるか数える (DP). この DP テーブルがあれば,h について (残りの要素, 次に割り当てるサイクル長) をキーに DP を行うことで解ける.
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long i64;
const i64 MOD = 1000000007;
inline void ADD(i64& a, i64 b) { a = (a + b) % MOD; }
i64 gcd(i64 a, i64 b) {
return b ? gcd(b, a % b) : a;
}
int N;
i64 X, Y, Z, K;
i64 C[2050][2050], frac[2050];
i64 dp[2][1010][1010];
i64 sol[1010][1010];
void solve(int t, i64 K)
{
i64 ip[1010];
for (int i = 1; i <= N; ++i) {
vector<int> good;
for (int j = 1; j <= N / i; ++j) {
if (j == gcd(j * i, K)) good.push_back(j);
}
ip[0] = 1;
for (int j = 1; j <= N / i; ++j) ip[j] = (ip[j - 1] * i) % MOD;
dp[t][i][0] = 1;
for (int j = 1; j <= N / i; ++j) {
dp[t][i][j] = 0;
for (int k : good) if (j >= k) {
ADD(dp[t][i][j], frac[k - 1] * ip[k - 1] % MOD * C[j - 1][k - 1] % MOD * dp[t][i][j - k]);
}
}
}
}
int main()
{
scanf("%d%lld%lld%lld", &N, &X, &Y, &Z);
K = X + Z - Y;
if (K < 0) K = -K;
for (int i = 0; i < 2050; ++i) C[0][i] = 0;
C[0][0] = 1;
for (int i = 1; i < 2050; ++i) {
C[i][0] = 1;
for (int j = 1; j < 2050; ++j) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
}
frac[0] = 1;
for (int i = 1; i < 2050; ++i) frac[i] = (frac[i - 1] * i) % MOD;
solve(0, 2);
solve(1, K);
if (X == 0 && Y == 0 && Z == 0) {
i64 ret = dp[0][1][N];
for (int i = 0; i < N; ++i) ret = (ret * N) % MOD;
printf("%lld\n", ret);
return 0;
}
for (int i = 0; i <= N; ++i) sol[1][i] = 0;
sol[1][N] = 1;
i64 cyc_cnt[1010];
for (int i = 1; i <= N; ++i) {
for (int j = 0; j <= N; ++j) sol[i + 1][j] = 0;
cyc_cnt[0] = 1;
for (int j = 1; j <= N / i; ++j) {
cyc_cnt[j] = cyc_cnt[j - 1] * C[i * j - 1][i - 1] % MOD * frac[i - 1] % MOD;
}
for (int j = 0; j <= N; ++j) {
for (int k = 0; k <= j / i; ++k) {
ADD(sol[i + 1][j - k * i], sol[i][j] * cyc_cnt[k] % MOD * C[j][k * i] % MOD * dp[0][i][k] % MOD * dp[1][i][k]);
}
}
}
printf("%lld\n", sol[N + 1][0]);
return 0;
}
2234 (Usagitobi)
{(ax+cy mod m, bx+dy mod n) | x, y ∈ Z} の元の個数を求めるとよい.