Ptz camp 的部分题。
题目是乱序的。
E3 Mountains
求 \(n \times m\) 的非负矩阵个数,满足 \((1, 1)\) 到 \((n, m)\) 的最大路径和不超过 \(k\)。答案对 \(10^9 + 7\) 取模。
\(1 \leq n, m, k \leq 100\)
最大路径和,有个经典DP。
\[ dp_{i, j} = a_{i, j} + \max \{ dp_{i - 1, j}, dp_{i, j - 1} \} \]
由 \(a_{i, j} \geq 0\),得到 \(dp_{i, j} \geq dp_{i - 1, j}, dp_{i, j} \geq dp_{i, j - 1}\)。
然后还有 \(dp_{n, m} \leq k\)。
由上面的等式可知a和dp数组可以相互转化,所以问题转化为求dp矩阵的个数。
画出三维的方块图,\(dp_{i, j}\) 表示 (i, j) 上有 \(dp_{i, j}\) 个方块,然后可以转化为不相交路径计数,用LGV引理解决。
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<Z>> bin(k + m + 1, std::vector<Z>(k + m + 1));
for (int i = 0; i <= k + m; i++) {
[i][0] = 1;
binfor (int j = 1; j <= i; j++) {
[i][j] = bin[i - 1][j] + bin[i - 1][j - 1];
bin}
}
std::vector<std::vector<Z>> a(n, std::vector<Z>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
[i][j] = m + j - i < 0 || j - i > k ? 0 : bin[k + m][m + j - i];
a}
}
= 1;
Z ans for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (a[j][i]) {
std::swap(a[i], a[j]);
break;
}
}
if (!a[i][i]) {
= 0;
ans break;
}
*= a[i][i];
ans for (int j = i + 1; j < n; j++) {
if (a[j][i]) {
= a[j][i] / a[i][i];
Z c for (int k = 0; k < n; k++) {
[j][k] -= c * a[i][k];
a}
}
}
}
std::cout << ans << "\n";
}
I3 Box Packing
给定 \(n\) 个二元组 \((a_i, b_i)\),按字典序有序。
求最长的子序列长度,满足该子序列可以划分成不超过k个a,b均单调不降的子序列。
\(1 \leq n \leq 10^5, 1 \leq k \leq 100\)
杨表。科技题,证明还non-trival,难搞哦。
19年集训队论文《浅谈杨氏矩阵在信息学竞赛中的应用》
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<PII> a(n);
for (int i = 0; i < n; i++) std::cin >> a[i].first >> a[i].second;
std::sort(a.begin(), a.end());
std::vector<std::vector<int>> p;
for (auto [_, x] : a) {
for (int i = 0; i <= p.size() && i < k; i++) {
if (i == p.size()) {
.push_back({x});
pbreak;
}
auto it = std::upper_bound(p[i].begin(), p[i].end(), x);
if (it == p[i].end()) {
[i].push_back(x);
pbreak;
}
std::swap(x, *it);
}
}
.resize(k);
pint ans = 0;
for (int i = 0; i < k; i++) {
+= p[i].size();
ans }
std::cout << ans << "\n";
}
K3 Fancy Arrays
求长度为 \(n\) 的序列 \(a\) 的数量,满足 \(a_i | m, \operatorname{gcd}(a_i, a_{i+1})>1\)。\(q\) 次询问 \(n\)。
\(m \leq 10^{16}, q \leq 150, n \leq 10^{18}\)。
\(m\) 质因数分解后,指数相同的质因子是等价的,划分成一个类,状态表示为等价类内选择了多少个质因子。
状态数 \(N\) 不超过256个,预处理出转移矩阵的幂 \(M, M^2, M^4, M^8, \dots\)。
这样每次矩阵乘一个向量是 \(O(N^2)\) 的。
总复杂度 \(O(N^3\log n + qN^2\log n)\)
这题还可以直接 BM,下面是牛逼群友的解释:
You need to count some number of paths in graph, number of paths is equal to sum of A^n * v, where A is transition matrix and v is initial vector. Any element of A^n*v follows linear recurrence defined by minimal polynomial of A, so answer for the problem also follows linear recurrence.
// Author: HolyK
// Created: Sun Feb 13 22:37:24 2022
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
return x < y ? x = y, 1 : 0;
}
using LL = long long;
using PII = std::pair<int, int>;
constexpr int P(1e9 + 7);
inline void inc(int &x, int y) {
+= y;
x if (x >= P) x -= P;
}
inline void dec(int &x, int y) {
-= y;
x if (x < 0) x += P;
}
inline int mod(LL x) { return x % P; }
int fpow(int x, int k = P - 2) {
int r = 1;
for (; k; k >>= 1, x = 1LL * x * x % P) {
if (k & 1) r = 1LL * r * x % P;
}
return r;
}
struct Z {
int x;
() : x(0) {}
Z(int v) : x(v < 0 ? v + P : v >= P ? v - P : v) {}
Z(LL v) : x((v %= P) < 0 ? v + P : v) {}
Zexplicit operator bool() { return !!x; }
() const { return Z(fpow(x)); }
Z inv(int k) const { return Z(fpow(x, k)); }
Z powoperator-() const { return Z(P - x); }
Z &operator+=(const Z &r) { return inc(x, r.x), *this; }
Z &operator-=(const Z &r) { return dec(x, r.x), *this; }
Z &operator*=(const Z &r) { return x = LL(x) * r.x % P, *this; }
Z &operator/=(const Z &r) { return x = LL(x) * fpow(r.x) % P, *this; }
Z inline friend Z operator+(const Z &a, const Z &b) { return Z(a) += b; }
inline friend Z operator-(const Z &a, const Z &b) { return Z(a) -= b; }
inline friend Z operator*(const Z &a, const Z &b) { return Z(a) *= b; }
inline friend Z operator/(const Z &a, const Z &b) { return Z(a) /= b; }
inline friend std::ostream &operator<<(std::ostream &os, const Z &r) {
return os << r.x;
}
};
constexpr int N(233 + 5);
int fac[N], ifac[N];
int bin(int n, int m) {
if (m < 0 || m > n) return 0;
return 1LL * fac[n] * ifac[m] % P * ifac[n - m] % P;
}
void init(int n) {
[0] = ifac[0] = 1;
facfor (int i = 1; i <= n; i++) fac[i] = 1LL * fac[i - 1] * i % P;
[n] = fpow(fac[n]);
ifacfor (int i = n - 1; i > 0; i--) ifac[i] = 1LL * ifac[i + 1] * (i + 1) % P;
}
using Poly = std::vector<int>;
using Matrix = std::vector<Poly>;
operator*(const Matrix &a, const Matrix &b) {
Matrix int n = a.size();
(n, Poly(n));
Matrix cfor (int i = 0; i < n; i++) {
for (int k = 0; k < n; k++) {
for (int j = 0; j < n; j++) {
[i][j] = (c[i][j] + 1LL * a[i][k] * b[k][j]) % P;
c}
}
}
return c;
}
operator*(const Poly &a, const Matrix &b) {
Poly int n = a.size();
(n);
Poly rfor (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
[j] = (r[j] + 1LL * a[i] * b[i][j]) % P;
r}
}
return r;
}
using LLL = __int128_t;
(LL x, LL k, LL p) {
LL fpow= 1;
LL r for (; k; k >>= 1, x = (LLL)x * x % p) {
if (k & 1) r = (LLL)r * x % p;
}
return r;
}
bool isPrime(LL p) {
if (p < 2) return false;
if (p == 2 || p == 3) return true;
= p - 1, r = 0;
LL d static std::mt19937 rnd(19260817);
for (; d & 1 ^ 1; d >>= 1) r++;
for (LL k = 0; k < 10; k++) {
= rnd() % (p - 2) + 2, x = fpow(a, d, p);
LL a if (x == 1 || x == p - 1) continue;
for (int i = 0; i < r - 1 && x != p - 1; i++) {
= (LLL)x * x % p;
x }
if (x != p - 1) return false;
}
return true;
}
(LL n) {
LL prauto f = [&](LL x) { return (LLL)x * x % n + 1; };
= 2, q;
LL p for (LL i = 1, x = 0, y = 0, t = 30; t++ % 40 || std::gcd(p, n) == 1; x = f(x), y = f(f(y))) {
if (x == y) x = ++i, y = f(x);
= (LLL)p * (x - y > 0 ? x - y : y - x) % n;
q if (q) p = q;
}
return std::gcd(p, n);
}
std::vector<LL> factor(LL n) {
if (n == 1) return {};
if (isPrime(n)) return {n};
= pr(n);
LL x auto l = factor(x), r = factor(n / x);
.insert(l.end(), r.begin(), r.end());
lreturn l;
}
void solve() {
, q, n;
LL mstd::cin >> m >> q;
auto fac = factor(m);
std::sort(fac.begin(), fac.end());
std::map<int, int> cnt;
for (int i = 0, j; i < fac.size(); i = j) {
for (j = i + 1; j < fac.size() && fac[i] == fac[j]; j++) ;
[j - i]++;
cnt}
// for (auto [x, y] :cnt) {
// std::cerr << "!cnt " << x << " " << y << "\n";
// }
(200);
init
std::vector<PII> a(cnt.begin(), cnt.end());
= 1;
LL tot for (auto [x, y] : a) tot *= y + 1;
// std::cerr << tot << "\n";
(tot, Poly(tot));
Matrix matfor (int i = 0; i < tot; i++) {
for (int j = 0; j < tot; j++) {
int x = i, y = j;
= 1, del = 1, sum = 1;
Z coef for (auto [c, k] : a) {
int u = x % (k + 1), v = y % (k + 1);
*= fpow(c, v);
coef *= bin(k, v);
sum *= bin(k - u, v);
del /= k + 1, y /= k + 1;
x }
*= sum - del;
coef [i][j] = coef.x;
mat}
}
std::vector<Matrix> pw(64);
[0] = mat;
pwfor (int i = 1; i < 64; i++) {
[i] = pw[i - 1] * pw[i - 1];
pw}
while (q--) {
std::cin >> n;
(tot);
Poly v.back() = 1;
vfor (int i = 0; i < 64; i++) {
if (n >> i & 1) {
= v * pw[i];
v }
}
int ans = 0;
for (int x : v) inc(ans, x);
if (n == 1) inc(ans, 1);
std::cout << ans << "\n";
}
}
int main() {
// freopen("t.in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
// std::cin >> t;
while (t--) {
();
solve}
return 0;
}
G3 Maximal Subsequence
给定序列 \(a_n\),求最长的子序列,满足其严格 LIS 小于原序列。
\(1 \leq n \leq 5 \times 10^5, 1 \leq a_i \leq 10^9\)。
https://dmoj.ca/problem/coci21c1p5/editorial
问题可以转化为求 \(n\) - 可以拿出多少个LIS。
直接按照字典序贪心的拿就是对的,证明见上面的链接。
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n), b, f(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
auto it = std::lower_bound(b.begin(), b.end(), a[i]);
[i] = it - b.begin();
fif (it == b.end()) {
.push_back(a[i]);
b} else {
*it = a[i];
}
}
int k = b.size(), ans = 0;
std::vector<std::vector<int>> g(k);
for (int i = n - 1; i >= 0; i--) g[f[i]].push_back(i);
std::function<bool(int, int)> dfs = [&](int l, int v) {
while (!g[l].empty()) {
int i = g[l].back();
if (a[i] <= v) return false;
[l].pop_back();
gif (l + 1 == k) return true;
while (!g[l + 1].empty() && g[l + 1].back() < i) {
[l + 1].pop_back();
g}
if (g[l + 1].empty() || a[g[l + 1].back()] <= a[i]) {
continue;
}
if (dfs(l + 1, a[i])) return true;
}
return false;
};
while (!g[0].empty()) {
if (dfs(0, -1)) ans++;
}
std::cout << n - ans << "\n";
}
C3 Inversions
\(n\) 的排列 \(p\) 的逆序对个数为 \(\operatorname{inv}(p)\),求 \(\operatorname{inv}(p) ^k \bmod 998244353\)。
\(1 \leq n \leq 10^{18}, 1 \leq k \leq 1000\)。
设 \(g(n, k)\) 表示答案。
\[ \begin{aligned} g(n+1, k) &= \sum_{i = 0}^n \sum_p (i + \operatorname{inv}(p))^k\\ &= \sum_{i=0}^n\sum_{j=0}^k \sum_p \operatorname{inv}(p)^j i^{k-j} \frac{k!}{j!(k-j)!}\\ \frac{g(n+1, k)}{k!} &= \sum_{j = 0}^k \frac{g(n, j)}{j!}\sum_{i=0}^n \frac{i^{k-j}}{(k - j)!}\\ G_{n + 1}(x) &= G_n(x) A_n(x)\\ A_n(x) &= \sum_{k\geq 0} \frac{x^k}{k!} \sum_{i = 0}^n i^k\\ &= \sum_{i = 0}^n e^{ix}\\ &= \frac{1 - e^{(n + 1)x}}{1 - e^x}\\ G_n(x) &= \prod_{k = 1}^n\frac{1 - e^{kx}}{1 - e^x} \end{aligned} \]
结论是 \(\dfrac{g(n, k)}{n!}\) 是关于 \(n\) 的 \(2k\) 次多项式,插值出这个多项式就行了。
需要算大数阶乘 \(n!\),可以分段打表。
void solve() {
();
init, k;
LL nstd::cin >> n >> k;
if (n >= P) {
std::cout << "0\n";
return;
}
int len = k * 2 + 1;
(len), p = {1}, v(k + 1);
Poly a.resize(k + 1);
p
for (int i = 0; i <= k; i++) {
[i] = ifac[i + 1];
v}
= v.inv(k + 1);
v
int s = norm(k * 2 + 1);
.resize(s);
v(v);
dft
[0] = p[k];
afor (int i = 1; i < len; i++) {
(k + 1);
Poly qfor (int j = 0, pw = 1; j <= k; j++) {
= 1LL * pw * i % P;
pw [j] = 1LL * ifac[j + 1] * pw % P;
q}
.resize(s);
q(q);
dft^= v;
q (q);
idft.resize(k + 1);
q= p * q;
p .resize(k + 1);
p[i] = 1LL * p[k] * fac[k] % P * ifac[i] % P;
a// std::cerr << i << " " << a[i] << "\n";
}
(len);
Poly bstd::iota(b.begin(), b.end(), 0);
= SegTree(b).interpolate(a);
Poly f // for (int x : f) std::cerr << x << " ";
// std::cerr << "\n";
int ans = 0;
for (int i = f.size() - 1; i >= 0; i--) {
= (ans * n + f[i]) % P;
ans }
auto it = --map.upper_bound(n);
= 1LL * ans * it->second % P;
ans for (int i = it->first + 1; i <= n; i++) {
= 1LL * ans * i % P;
ans }
std::cout << ans << "\n";
}
UPD 2022.05.11
反转了,上面这个做法实在是太菜了,令 \(F(x) = \dfrac{e^x-1}{x}\),则 \(\displaystyle{G(x) = n!\prod_{i=1}^n\frac{F(ix)}{F(x)}}\),先 \(\ln\) 再 \(\exp\) 即可,需要处理 \(n^i(i = 0, 1, \dots, k)\) 的自然数幂和,用 \(e^{ax}\) 等比数列求和即可。
复杂度 \(O(k\log k)\).
void solve() {
();
init;
LL nint k;
std::cin >> n >> k;
if (n >= P) {
std::cout << "0\n";
return;
}
(k + 1), q(k + 1);
Poly pfor (int i = 0; i <= k; i++) {
[i] = 1LL * ifac[i + 1] * (fpow(n + 1, i + 1) + P - 1) % P;
p[i] = ifac[i + 1];
q}
= (p * q.inv(k + 1)).pre(k + 1);
p
= q.log(k + 1);
Poly f for (int i = 0; i <= k; i++) {
[i] = 1LL * p[i] * fac[i] % P;
p[i] = 1LL * f[i] * (p[i] + P - n) % P;
f}
= f.exp(k + 1);
f
int ans = f[k];
auto it = --map.upper_bound(n);
= 1LL * ans * it->second % P;
ans for (int i = it->first + 1; i <= n; i++) {
= 1LL * ans * i % P;
ans }
std::cout << 1LL * ans * fac[k] % P << "\n";
}
K1 King’s Palace
\(n\) 个物品,每个物品可以染RGB三种颜色。有 \(m\) 个限制,形如 \((a, x, b, y)\),表示第 \(a\) 个物品的颜色是 \(x\),第 \(b\) 个物品的颜色是 \(y\) 的情况是不合法的。
求合法染色方案数。
\(1 \leq n \leq 22, 1 \leq m \leq 9n(n-1)/2, 1 \leq a < b \leq n\)。
Meet in the middle.
分成 \(t, n - t\) 两个部分,对于跨两个部分的限制,将第 \(i\) 个物品的第 \(j\) 个颜色是否可以使用压成 \(3t\) 位二进制,使用高维前缀和计算。
取 \(t = 7\) 可以通过。
复杂度可以是 \(O(3^t + 3^{n - t} + (n - t)2^{3(n-t)})\),前半部分图方便随便写了,后半部分 \(3^{n-t}\) 是关键。
void solve() {
int n, m;
std::cin >> n >> m;
int t = std::min(n, 7);
std::vector<std::array<int, 4>> lim(m);
for (int i = 0; i < m; i++) {
int a, b, c, d;
std::string s, t;
std::cin >> a >> s >> b >> t;
= s[0] == 'R' ? 0 : s[0] == 'G' ? 1 : 2;
c = t[0] == 'R' ? 0 : t[0] == 'G' ? 1 : 2;
d --, b--;
a[i] = {a, c, b, d};
lim// std::cerr << a << " " << b << " " << c << " " << d << "\n";
}
std::vector<int> pw(n + 1);
[0] = 1;
pwfor (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 3;
std::vector<int> s(1 << t * 3);
for (int i = 0; i < pw[t]; i++) {
bool ok = true;
for (auto &[a, b, c, d] : lim) {
if (a >= t || c >= t) continue;
if (i / pw[a] % 3 == b && i / pw[c] % 3 == d) {
= false;
ok break;
}
}
if (!ok) continue;
int sta = 0;
for (int j = 0, k = i; j < t; j++, k /= 3) {
|= 1 << 3 * j + k % 3;
sta }
[sta]++;
s}
for (int i = 1; i < s.size(); i *= 2) {
for (int j = 0; j < s.size(); j += i * 2) {
for (int k = 0; k < i; k++) {
[i + j + k] += s[j + k];
s}
}
}
std::vector<int> banl((n - t) * 3);
std::vector<std::vector<PII>> banr((n - t) * 3);
for (auto &[a, b, c, d] : lim) {
if (c >= t) {
if (a < t) {
[(c - t) * 3 + d] |= 1 << a * 3 + b;
banl} else {
[(a - t) * 3 + b].push_back({c - t, d});
banr}
}
}
= 0;
LL ans std::vector ban(n - t, std::vector<int>(3));
// std::cerr << n - t << "\n";
std::function<void(int, int)> dfs = [&](int p, int sta) {
// std::cerr << p << " " << sta << "\n";
if (p == n - t) {
// assert(sta < s.size() && sta >= 0);
+= s[s.size() - 1 ^ sta];
ans return;
}
for (int i = 0; i < 3; i++) {
if (!ban[p][i]) {
// assert(p * 3 + i < banl.size());
// assert(p * 3 + i < banr.size());
for (auto &[x, y] : banr[p * 3 + i]) {
// assert(y < 3);
[x][y]++;
ban}
(p + 1, sta | banl[p * 3 + i]);
dfsfor (auto &[x, y] : banr[p * 3 + i]) {
[x][y]--;
ban}
}
}
};
(0, 0);
dfsstd::cout << ans << "\n";
}
M1 Math String
求长度为 \(n\) 的所有合法的只包含
+
*
运算和数字 \(1 \dots 9\) 的算术表达式的结果之和。\(1 \leq n \leq 10^{18}\)。
tourist 的方法是暴力搜出表达式的形状,后面再填数计算结果,这个大概是比 \(3^n\) 小的。
算出 20 项之后直接 BM。
递推式是6阶的。
这个暴力是比较容易想的一个做法,但是为啥是 linear recurrence 呢。
TG 老哥给出了生成函数的思路:
纯数字的答案, \[ \begin{aligned} a_n &= 9a_{n - 1} + (1 + 2 + \dots + 9) \times 10^{n-1}\times 9^{n-1}\\ A(x) &= 9xA(x) + \frac{45x}{1 - 90x}\\ A(x) &= \frac{45x}{(1-90x)(1-9x)} \end{aligned} \]
带 * 的答案 \[ \begin{aligned} B(x) &= A(x) + A^2(x)x + A^3(x)x^2 + \cdots\\ &= \dfrac{A(x)}{1 - xA(x)}\\ &= \dfrac{45x}{1-99x+765x^2} \end{aligned} \]
表达式的方案数, \[ \begin{aligned} c_0 &= 0,c_1 = 9,\\ c_n &= 9c_{n-1}+18c_{n-2}, \\ C(x) &= 9x + 9xC(x) + 18x^2C(x)\\ C(x) &= \frac{9x}{1 - 9x - 18x^2} \end{aligned} \]
最终答案(没有+的表达式,在边上的表达式的贡献,在中间的表达式的贡献) \[ \begin{aligned} F(x) &= B(x) + 2B(x)xC(x) + B(x) x^2C(x)\\ &= B(x)(1 + xC(x))^2 \end{aligned} \]
综上所述, \(F(x) = \dfrac{P(x)}{Q(x)}\) 且 \(P(x), Q(x)\) 均为多项式,符合线性递归的形式。
void solve() {
= {1, P - 9, P - 9}, q = {1, P - 9, P - 18};
Poly p = p * p * Poly{0, 45}, q = q * q * Poly{1, P - 99, 765};
p ;
LL nstd::cin >> n;
std::cout << divAt(p, q, n) << "\n";
}
最后修改于 2022-06-01