特辑:卡特兰数 & Prufer & BSGS
总共9道题怎么显示我A了10道(
Part 1: 卡特兰数:
Problem A:网格
不是卡特兰数,但推导过程类似。
首先A到B总步数为曼哈顿距离 $ m + n $ .于是总方案数为 $ C_{m + n} ^ m $.
考虑非法的情况,把所有跨越那条分界线的路径沿恰好跨过的那条线翻折,等价于从 \((0, 0)\) 到 \((m - 1, n + 1)\),非法方案数为 $ C_{m + n} ^ {m - 1} $。
数太大啦,用质因数分解 + BigInt 求。
BigInt 真好用 x1
#include <bits/stdc++.h>
int n, m, mip[10005], pri[10005], cnt[10005][10005], fin[10005];
void init(int t) {
for (int i = 2; i <= t; i++) {
if (!mip[i]) mip[i] = i, pri[++pri[0]] = i;
for (int j = 1; j <= pri[0] && i * pri[j] <= t; j++) {
mip[i * pri[j]] = pri[j];
if (i % pri[j] == 0) break;
}
}
for (int i = 2; i <= t; i++) {
for (int j = 1; j <= pri[0]; j++) {
int x = i;
while (x % pri[j] == 0) ++cnt[i][j], x /= pri[j];
cnt[i][j] += cnt[i - 1][j];
}
}
}
struct BigInt {
int l, a[23333], base;
BigInt() {
l = 0, base = 10;
memset(a, 0, sizeof(a));
}
BigInt Trans(int x) {
BigInt y;
while (x)
y.a[++y.l] = x % y.base, x /= y.base;
return y;
}
friend BigInt operator +(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++)
z.a[i] = x.a[i] + y.a[i], z.a[i + 1] += z.a[i] / x.base, x.a[i] %= x.base;
if (z.a[z.l + 1]) z.l++;
return z;
}
friend BigInt operator +(BigInt x, int y) {
BigInt tmp = tmp.Trans(y);
return x + tmp;
}
friend BigInt operator -(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++) {
if (x.a[i] < y.a[i]) x.a[i] += x.base, x.a[i + 1]--;
z.a[i] = x.a[i] - y.a[i];
}
while (!z.a[z.l] && z.l) z.l--;
if (z.l == 0) z.a[1] = 1, z.l = 1;
return z;
}
friend BigInt operator *(BigInt x, BigInt y) {
BigInt z;
z.l = x.l + y.l;
if ((x.l == 1 && x.a[1] == 0) || (y.l == 1 && y.a[1] == 0)) {
z.l = 1;
return z;
}
for (int i = 1; i <= x.l; i++)
for (int j = 1; j <= y.l; j++)
z.a[i + j - 1] += x.a[i] * y.a[j], z.a[i + j] += z.a[i + j - 1] / x.base, z.a[i + j - 1] %= x.base;
while (!z.a[z.l] && z.l) z.l--;
if (!z.l) {z.l = 1, z.a[1] = 0;}
return z;
}
friend BigInt operator *(BigInt x, int y) {
BigInt z; int l = x.l;
for (int i = 1; i <= l; i++)
z.a[i] += x.a[i] * y, z.a[i + 1] += z.a[i] / x.base, z.a[i] %= x.base;
while (z.a[l + 1])
l++, z.a[l + 1] += z.a[l] / x.base, z.a[l] %= x.base;
z.l = l;
while (!z.a[z.l]) z.l--;
return z;
}
friend BigInt operator /(BigInt x, int y) {
BigInt z; z.l = x.l;
int t = 0;
for (int i = x.l; i >= 1; i--)
t = t * 10 + x.a[i], z.a[i] = t / y, t %= y;
while (!z.a[z.l]) z.l--;
return z;
}
void print() {
for (int i = l; i >= 1; i--)
printf("%d", a[i]);
printf("\n");
}
};
BigInt Qpow(int x, int p) {
BigInt ret = ret.Trans(1), tmp = tmp.Trans(x);
for (; p; p >>= 1, tmp = tmp * tmp)
if (p & 1) ret = ret * tmp;
return ret;
}
signed main() {
scanf("%d%d", &n, &m);
init(n+ m);
for (int i = 1; i <= pri[0]; i++)
fin[i] = cnt[n + m][i] + cnt[n - m + 1][i] - cnt[n - m][i] - cnt[m][i] - cnt[n + 1][i];
BigInt ans; ans.l = 1, ans.a[1] = 1;
for (int i = 1; i <= pri[0]; i++)
ans = ans * Qpow(pri[i], fin[i]);
ans.print();
return 0;
}
Problem B: 有趣的数列
和出栈入栈没区别,裸卡特兰数。
数太大啦,用质因数分解
能取模,不用BigInt了(
#include <bits/stdc++.h>
#define ll long long
ll ans = 1;
int n, p, pri[10000005], mip[10000005], cnt[10000005];
ll Qpow(int x, int b, int p) {
ll ret = 1;
for (; b; b >>= 1, x = x * x % p)
if (b & 1) ret = ret * x % p;
return ret;
}
signed main() {
scanf("%d%d", &n, &p);
for (int i = 2; i <= n; i++) cnt[i] = -1;
for (int i = n + 2; i <= 2 * n; i++) cnt[i] = 1;
for (int i = 2; i <= 2 * n; i++) {
if (!mip[i]) mip[i] = i, pri[++pri[0]] = i;
for (int j = 1; j <= pri[0] && pri[j] * i <= 2 * n; j++) {
mip[pri[j] * i] = pri[j];
if (i % pri[j] == 0) break;
}
}
for (int i = 2 * n; i >= 2; i--) {
if (mip[i] != i) {
cnt[mip[i]] += cnt[i];
cnt[i / mip[i]] += cnt[i];
} else ans = (ans * Qpow(i, cnt[i], p)) % p;
}
printf("%lld\n", ans);
return 0;
}
Problem C: 树屋阶梯
没那么裸的卡特兰数
n阶阶梯分成n块,那么每一块必定包含一个"突起"。
而我们关注包含左下角底角那块,它的宽度可能为1 - n.而它可以把阶梯分成0阶和n-1阶,1阶和n-2阶.....
最后答案为:\(f(n) = f(0)f(n-1) + f(1)(n-2) + ...\),这玩意就是卡特兰数。
数太大啦,用质因...
不用,用递推的那个卡特兰数公式(
然而还是要BigInt
BigInt 真好用 x2
#include <bits/stdc++.h>
int n;
struct BigInt {
int l, a[2333], base;
BigInt() {
l = 0, base = 10;
memset(a, 0, sizeof(a));
}
friend BigInt operator +(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++)
z.a[i] = x.a[i] + y.a[i], z.a[i + 1] += z.a[i] / x.base, x.a[i] %= x.base;
if (z.a[z.l + 1]) z.l++;
return z;
}
friend BigInt operator -(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++) {
if (x.a[i] < y.a[i]) x.a[i] += x.base, x.a[i + 1]--;
z.a[i] = x.a[i] - y.a[i];
}
while (!z.a[z.l] && z.l) z.l--;
if (z.l == 0) z.a[1] = 1, z.l = 1;
return z;
}
friend BigInt operator *(BigInt x, BigInt y) {
BigInt z;
z.l = x.l + y.l;
if ((x.l == 1 && x.a[1] == 0) || (y.l == 1 && y.a[1] == 0)) {
z.l = 1, z.a[1] = 0;
return z;
}
for (int i = 1; i <= x.l; i++)
for (int j = 1; j <= y.l; j++)
z.a[i + j - 1] += x.a[i] * y.a[j], z.a[i + j] += z.a[i + j - 1] / x.base, z.a[i + j - 1] %= x.base;
if (z.a[z.l + 1]) z.l++;
return z;
}
friend BigInt operator *(BigInt x, int y) {
BigInt z; int l = x.l;
for (int i = 1; i <= l; i++)
z.a[i] += x.a[i] * y, z.a[i + 1] += z.a[i] / x.base, z.a[i] %= x.base;
while (z.a[l + 1])
l++, z.a[l + 1] += z.a[l] / x.base, z.a[l] %= x.base;
z.l = l;
return z;
}
friend BigInt operator /(BigInt x, int y) {
BigInt z; z.l = x.l;
int t = 0;
for (int i = x.l; i >= 1; i--)
t = t * 10 + x.a[i], z.a[i] = t / y, t %= y;
while (!z.a[z.l]) z.l--;
return z;
}
void print() {
for (int i = l; i >= 1; i--)
printf("%d", a[i]);
printf("\n");
}
};
signed main() {
scanf("%d", &n);
BigInt Cat[505];
Cat[1].l = 1, Cat[1].a[1] = 1;
for (int i = 2; i <= n; i++)
Cat[i] = Cat[i - 1] * (4 * i - 2) / (i + 1);
Cat[n].print();
return 0;
}
Part 2: Prufer序列
Problem D: 树的计数
根据Prufer序列的性质,树节点数为n,Prufer序列长为n - 2,每个点出现度数-1次。
所以数的种数为\(C_{n - 2} ^ {d_1 - 1} C_{n - 2 - (d_1 - 1)} ^ {d_2 - 1} ...\) ,化简得:\(\frac{(n - 2) !}{\prod \limits_{i = 1}^{n}(d_i - 1)!}\).
数不大了,不用BigInt啦
但分解质因数还是要的(
#include <bits/stdc++.h>
#define ll long long
int n, d[155], sum, pri[500], mip[500], cnt[500][500], ans[500];
ll QAQ;
ll Qpow(int x, int b) {
ll ret = 1;
for (; b; b >>= 1, x *= x)
if (b & 1) ret *= x;
return ret;
}
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", d + i);
if (d[i] == 0 && n > 1) {
puts("0");
return 0;
}
sum += d[i] - 1;
}
if (sum != n - 2) {
puts("0");
return 0;
}
for (int i = 2; i <= n; i++) {
if (mip[i] != i) mip[i] = i, pri[++pri[0]] = i;
for (int j = 1; j <= pri[0] && i * pri[j] <= n; j++) {
mip[i * pri[j]] = pri[j];
if (i % pri[j] == 0) break;
}
}
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= pri[0]; j++) {
cnt[i][j] = 0;
int x = i;
while (x % pri[j] == 0) cnt[i][j]++, x /= pri[j];
cnt[i][j] += cnt[i - 1][j];
}
}
for (int i = 1; i <= pri[0]; i++) ans[i] = cnt[n - 2][i];
QAQ = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= pri[0]; j++)
ans[j] -= cnt[d[i] - 1][j];
for (int i = 1; i <= pri[0]; i++)
QAQ = QAQ * Qpow(pri[i], ans[i]);
printf("%lld\n", QAQ);
return 0;
}
Problem E: 明明的烦恼
首先给出度数k个点,当前Prufer序列总长\(sum = \sum \limits_{i = 1} ^ k (d_i - 1)\), 它对答案贡献为 \(C_{n - 2} ^ {sum} \frac{sum!}{\prod \limits_{i = 1}^k (d_i - 1)!}\).
剩下的点填满 $ n - sum - 2 $ 个空位,贡献为 $ (n - k) ^ {n - sum - 2} $.
最终答案:$ C_{n - 2} ^ {sum} \frac{sum!}{\prod \limits_{i = 1}^k (d_i - 1)!} (n - k) ^ {n - sum - 2} $.
化简:\(\frac{(n - 2)!}{ (n - sum - 2)! \prod \limits_{i = 1}^k (d_i - 1)!} (n - k) ^ {n - sum - 2}\)
数太大啦,用质因数分解 + BigInt 求。
BigInt 真好用 x3
#include <bits/stdc++.h>
int n, d[1005], QAQ, pri[1005], mip[1005], cnt[1005][1005], fin[1005], sum;
void init(int t) {
for (int i = 2; i <= t; i++) {
if (!mip[i]) mip[i] = i, pri[++pri[0]] = i;
for (int j = 1; j <= pri[0] && i * pri[j] <= t; j++) {
mip[i * pri[j]] = pri[j];
if (i % pri[j] == 0) break;
}
}
for (int i = 2; i <= t; i++) {
for (int j = 1; j <= pri[0]; j++) {
int x = i;
while (x % pri[j] == 0) ++cnt[i][j], x /= pri[j];
cnt[i][j] += cnt[i - 1][j];
}
}
}
struct BigInt {
int l, a[23333], base;
BigInt() {
l = 0, base = 10;
memset(a, 0, sizeof(a));
}
BigInt Trans(int x) {
BigInt y;
while (x)
y.a[++y.l] = x % y.base, x /= y.base;
return y;
}
friend BigInt operator +(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++)
z.a[i] = x.a[i] + y.a[i], z.a[i + 1] += z.a[i] / x.base, x.a[i] %= x.base;
if (z.a[z.l + 1]) z.l++;
return z;
}
friend BigInt operator +(BigInt x, int y) {
BigInt tmp = tmp.Trans(y);
return x + tmp;
}
friend BigInt operator -(BigInt x, BigInt y) {
BigInt z;
z.l = std::max(x.l, y.l);
for (int i = 1; i <= z.l; i++) {
if (x.a[i] < y.a[i]) x.a[i] += x.base, x.a[i + 1]--;
z.a[i] = x.a[i] - y.a[i];
}
while (!z.a[z.l] && z.l) z.l--;
if (z.l == 0) z.a[1] = 1, z.l = 1;
return z;
}
friend BigInt operator *(BigInt x, BigInt y) {
BigInt z;
z.l = x.l + y.l;
if ((x.l == 1 && x.a[1] == 0) || (y.l == 1 && y.a[1] == 0)) {
z.l = 1;
return z;
}
for (int i = 1; i <= x.l; i++)
for (int j = 1; j <= y.l; j++)
z.a[i + j - 1] += x.a[i] * y.a[j], z.a[i + j] += z.a[i + j - 1] / x.base, z.a[i + j - 1] %= x.base;
while (!z.a[z.l] && z.l) z.l--;
if (!z.l) {z.l = 1, z.a[1] = 0;}
return z;
}
friend BigInt operator *(BigInt x, int y) {
BigInt z; int l = x.l;
for (int i = 1; i <= l; i++)
z.a[i] += x.a[i] * y, z.a[i + 1] += z.a[i] / x.base, z.a[i] %= x.base;
while (z.a[l + 1])
l++, z.a[l + 1] += z.a[l] / x.base, z.a[l] %= x.base;
z.l = l;
while (!z.a[z.l]) z.l--;
return z;
}
friend BigInt operator /(BigInt x, int y) {
BigInt z; z.l = x.l;
int t = 0;
for (int i = x.l; i >= 1; i--)
t = t * 10 + x.a[i], z.a[i] = t / y, t %= y;
while (!z.a[z.l]) z.l--;
return z;
}
void print() {
for (int i = l; i >= 1; i--)
printf("%d", a[i]);
printf("\n");
}
};
BigInt Qpow(int x, int p) {
BigInt ret = ret.Trans(1), tmp = tmp.Trans(x);
for (; p; p >>= 1, tmp = tmp * tmp)
if (p & 1) ret = ret * tmp;
return ret;
}
signed main() {
scanf("%d", &n);
init(1000);
for (int i = 1, x; i <= n; i++) {
scanf("%d", &x);
if (x != -1) d[++QAQ] = x, sum += x - 1;
}
BigInt ans = ans.Trans(1);
ans = ans * Qpow(n - QAQ, n - 2 - sum);
for (int i = 1; i <= pri[0]; i++) {
fin[i] += cnt[n - 2][i] - cnt[n - sum - 2][i];
for (int j = 1; j <= QAQ; j++)
fin[i] -= cnt[d[j] - 1][i];
}
for (int i = 1; i <= pri[0]; i++)
ans = ans * Qpow(pri[i], fin[i]);
ans.print();
return 0;
}
Part 3: BSGS 百事公司
Problem F: 随机数生成器
数列神题(
\(x_{i+1} = (ax_i + b) \mod p\),使用构造法,\(x_{i + 1} + \lambda = a(x_i + \lambda) \mod p\).
解得:\(\lambda = \frac{b}{a - 1}\).
则数列\({x_i}\)通项公式为\(x_i = (x_1 + \frac{b}{a - 1}) a^{i - 1}\).
当\(x_i = t\)时,\((x_1 + \frac{b}{a - 1}) a^{i - 1} = t\),即:\(a_{i - 1} \equiv t \times (x_1 + \frac{b}{a - 1}) ^ {-1} \mod p\).
这是BSGS的形式,根据这玩意解就万事了。
关于BSGS 拔山盖世,它用于求解 \(a^x \equiv b \mod p\) 这样的同余方程。
主要思路:把 \(x\) 拆成 \(i * m - j\), \(a^{i * m} \equiv b * a ^ {j} \mod p\).
从0 - m 枚举j算出右式的值塞到Hash Table,由于拆成减的形式,j越大越优。
最后枚举i,求左式,在Hash Table找对应值,找到就是答案,最后找不到无解。
因为我很懒,天天用map
#include <bits/stdc++.h>
long long p, a, b, x_1, t, T, tt;
long long Qpow(long long x, long long y, long long p) {
long long ret = 1;
for (; y; y >>= 1, x = x * x % p)
if (y & 1) ret = ret * x % p;
return ret;
}
long long BSGS(long long x, long long y, long long p) {
long long m = ceil(sqrt(p)), t = 1;
std::map<long long, long long> hash;
for (int i = 0; i <= m; i++, y = y * x % p) hash[y] = i;
long long f = Qpow(a, m, p);
for (int i = 1; i <= m; i++) {
t = t * f % p;
if (hash.count(t)) return i * m - hash[t] + 1;
}
return -1;
}
signed main() {
scanf("%lld", &T);
while (T--) {
scanf("%lld%lld%lld%lld%lld", &p, &a, &b, &x_1, &t);
if (x_1 == t) {puts("1"); continue;}
if (a == 0) {puts(b == t ? "2" : "-1"); continue;}
if (a == 1) {
if (b == 0) {puts("-1"); continue;}
long long c = Qpow(b, p - 2, p) % p, d = (t - x_1 + p) % p;
printf("%lld\n", d * c % p + 1);
continue;
}
long long u = b * Qpow(a - 1, p - 2, p) % p;
tt = ((t + u) * Qpow((x_1 + u) % p, p - 2, p)) % p;
printf("%lld\n", BSGS(a, tt, p));
}
return 0;
}
Problem G: 计算器
模板三合一,不说了,秒了
#include <bits/stdc++.h>
#define ll long long
int T, opt;
ll y, z, p;
ll Qpow(ll x, int b, ll p) {
ll ret = 1;
for (; b; b >>= 1, x = x * x % p)
if (b & 1) ret = ret * x % p;
return ret;
}
ll BSGS(ll y, ll z, ll p) {
std::map<ll, ll> hash;
ll m = ceil(sqrt(p)), t = 1;
for (int i = 0; i <= m; i++, z = z * y % p) hash[z] = i;
ll s = Qpow(y, m, p);
for (int i = 1; i <= m; i++)
if (t = t * s % p, hash.count(t)) return i * m - hash[t];
return -1;
}
signed main() {
scanf("%d%d", &T, &opt);
while (T--) {
scanf("%lld%lld%lld", &y, &z, &p);
ll ans = 0;
if (opt == 1) ans = Qpow(y, z, p) % p;
else if (opt == 2) ans = (y % p != 0) ? z * Qpow(y, p - 2, p) % p : -1;
else ans = BSGS(y % p, z % p, p);
if (ans == -1 || (ans == 0 && opt == 3)) puts("Orz, I cannot find x!");
else printf("%lld\n", ans);
}
return 0;
}
Problem H: Discrete Logging
真BSGS板子,秒了
#include <bits/stdc++.h>
#define ll long long
ll p, b, n;
ll Qpow(ll x, int b, ll p) {
ll ret = 1;
for (; b; b >>= 1, x = x * x % p)
if (b & 1) ret = ret * x % p;
return ret;
}
ll BSGS(ll a, ll n, ll p) {
std::map<ll, ll> hash;
ll m = ceil(sqrt(p)), t = 1;
for (int i = 0; i <= m; i++, n = n * a % p) hash[n] = i;
ll f = Qpow(a, m, p);
for (int i = 1; i <= m; i++)
if (t = t * f % p, hash.count(t)) return i * m - hash[t];
return -1;
}
signed main() {
while (scanf("%lld%lld%lld", &p, &b, &n) != EOF) {
ll bsgs = BSGS(b, n, p);
if (bsgs == -1) puts("no solution");
else printf("%lld\n", bsgs);
}
return 0;
}
Problem I: Matrix
写个矩阵类,把乘法什么的封装起来。
仍然懒得写Hash Table,重载小于号塞map,其余照旧
秒了
#include <bits/stdc++.h>
int n, p;
struct Matrix {
int a[75][75];
friend Matrix operator *(Matrix x, Matrix y) {
Matrix z = {};
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
z.a[i][j] = (z.a[i][j] + x.a[i][k] * y.a[k][j] % p) % p;
return z;
}
void read(int x) {
for (int i = 1; i <= x; i++)
for (int j = 1; j <= x; j++)
scanf("%d", &a[i][j]);
}
friend bool operator <(Matrix x, Matrix y) {
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if (x.a[i][j] != y.a[i][j]) return x.a[i][j] < y.a[i][j];
return 0;
}
} A, B;
void BSGS() {
std::map<Matrix, int> hash;
int m = ceil(sqrt(p));
for (int i = 0; i <= m; i++, B = B * A) hash[B] = i;
Matrix f = A;
for (int i = 1; i <= m - 1; i++) f = f * A;
Matrix t = f;
for (int i = 1; i <= m; i++, t = t * f)
if (hash.count(t)) printf("%d\n", i * m - hash[t]), exit(0);
}
signed main() {
scanf("%d%d", &n, &p);
A.read(n), B.read(n);
BSGS();
return 0;
}