20250823
组合计数专题,选记一些。
T4
水の三角
枚举走了几个斜的,剩下就是基础反射容斥了。
代码
#include <iostream>
#define int long long
using namespace std;
const int P = 998244353;
int qpow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int fac[2000005], ifac[2000005], inv[2000005];
void Cpre(int n) {
fac[0] = fac[1] = ifac[0] = ifac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++) {
fac[i] = fac[i - 1] * i % P;
inv[i] = (P - P / i) * inv[P % i] % P;
ifac[i] = ifac[i - 1] * inv[i] % P;
}
}
inline int C(int n, int m) { return n < 0 || m < 0 || n < m ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P; }
signed main() {
Cpre(2000000);
int tc;
cin >> tc;
while (tc--) {
int x, n = 1, m;
cin >> x;
int l = 1, r = 1000000, mid;
while (l <= r) {
mid = (l + r) >> 1;
if (mid * (mid + 1) / 2 >= x) n = mid, r = mid - 1;
else l = mid + 1;
}
m = x - n * (n - 1) / 2;
--n, --m;
int ans = 0;
for (int i = 0; i <= m; i++) ans += (C(n + m - i * 2, n - i) + P - C(n + m - i * 2, m - i - 1)) * C(n + m - i, i) % P;
cout << ans % P << "\n";
}
return 0;
}
T5
残暴圣所
对每个数对算贡献。会发现下标差一样的数对贡献系数都相同,于是只需要一遍差卷积求出差为 \(i\) 的数对的乘积之和,剩下的卡特兰数一下即可。
代码
#include <iostream>
#include <vector>
#define int long long
using namespace std;
const int P = 998244353;
const int g = 3, gi = 332748118;
int fac[2000005], ifac[2000005], inv[2000005];
void Cpre(int n) {
fac[0] = fac[1] = ifac[0] = ifac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++) {
fac[i] = fac[i - 1] * i % P;
inv[i] = (P - P / i) * inv[P % i] % P;
ifac[i] = ifac[i - 1] * inv[i] % P;
}
}
inline int C(int n, int m) { return n < 0 || m < 0 || n < m ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P; }
using poly = vector<int>;
int qpow(int x, int y = P - 2) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int r[4000005];
void NTT(vector<int> &f, int opt = 1) {
int n = f.size(), m = 1; while (m < n) m <<= 1;
f.resize(n = m);
for (int i = 1; i < n; i++) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
r[i] < i ? swap(f[r[i]], f[i]) : void();
}
for (int i = 1; i < n; i <<= 1) {
int w = qpow(opt != 1 ? gi : g, (P - 1) / (i << 1));
for (int j = 0; j < n; j += (i << 1)) {
for (int k = 0, t = 1; k < i; k++, t = t * w % P) {
int x = f[j | k], y = t * f[i | j | k] % P;
f[j | k] = (x + y) % P;
f[i | j | k] = (x - y + P) % P;
}
}
}
if (opt != 1) {
int x = qpow(n);
for (int i = 0; i < n; i++) f[i] = f[i] * x % P;
}
}
int Cat(int n) { return (C(n * 2, n) - C(n * 2, n - 1) + P) % P; }
poly F, G;
int n;
int a[1000005];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
Cpre(2000000);
cin >> n;
for (int i = 1; i <= n * 2; i++) cin >> a[i];
F.resize(n << 2 | 1), G.resize(n << 2 | 1);
for (int i = 1; i <= n * 2; i++) F[i] = a[i], G[i] = a[n * 2 - i + 1];
NTT(F), NTT(G);
for (int i = 0; i < (int)F.size(); i++) F[i] = F[i] * G[i] % P;
NTT(F, -1);
int ans = 0;
for (int i = 1; i <= n * 2; i += 2) {
int x = F[n * 2 + 1 + i];
ans += x * Cat((n * 2 - i - 1) / 2) % P * Cat((i - 1) >> 1) % P;
}
cout << ans % P << "\n";
return 0;
}
T6
过河卒二
从不定格以不定方向走出 \(n \times m\) 网格图右上边界 \(\longrightarrow\) 走到 \((n + 1, m + 1)\)
剩下就是基础 dp 和容斥了,也是要枚举走了几个斜的。
代码
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int P = 59393;
int fac[60005], ifac[60005], inv[60005];
void Cpre(int n) {
fac[0] = fac[1] = ifac[0] = ifac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++) {
fac[i] = fac[i - 1] * i % P;
inv[i] = (P - P / i) * inv[P % i] % P;
ifac[i] = ifac[i - 1] * inv[i] % P;
}
}
inline int _C(int n, int m) { return n < 0 || m < 0 || n < m ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P; }
inline int C(int n, int m) { return n < 0 || m < 0 || n < m ? 0 : (n == 0 ? (m == 0) : C(n / P, m / P) * _C(n % P, m % P) % P); }
int n, m, K;
pair<int, int> p[25];
int calc(int x, int y) {
int ret = 0;
for (int k = 0; k <= min(x, y); k++) ret += C(x + y - k, k) * C(x + y - k * 2, x - k) % P;
return ret;
}
int f[25];
signed main() {
Cpre(P - 1);
cin >> n >> m >> K;
for (int i = 1; i <= K; i++) cin >> p[i].first >> p[i].second;
sort(p + 1, p + K + 1);
int ans = calc(n, m);
for (int i = 1; i <= K; i++) {
int x = p[i].first, y = p[i].second;
f[i] = calc(x - 1, y - 1);
for (int j = 1; j < i; j++) {
if (p[j].first <= x && p[j].second <= y)
f[i] += P - f[j] * calc(x - p[j].first, y - p[j].second) % P;
}
f[i] %= P;
ans += P - f[i] * calc(n + 1 - x, m + 1 - y) % P;
}
cout << ans % P << "\n";
return 0;
}
T7
看电影
考虑数合法方案。在一个合法方案中,每个人都能够一直走下去,直到一个空座位。因此我们把这样美好的希望放回序列,发现只需要把序列连成一个环就可以让每个人一直走下去之后能遇到一个空座位。但是这个时候我们没法判断一个方案到底真合法还是走过了 \(k \rightarrow 1\) 边,于是在 \(k\) 后面摆一个 \(k + 1\) 座位用来钓鱼,这样如果 \(k + 1\) 座位有人坐了我们就知道这其实不是一个合法方案。由于这是一个环,我们的 \(k + 1\) 是可以随便乱移的。显然合法的位置只有 \(k + 1 - n\) 种,因为所有人落座之后只有这么多位置是空的。因为我们要数的是合法方案数,因此 \(k + 1\) 只能放在空位。然后这个时候所有人都变成在 \(k + 1\) 个里面选一个。注意在确定了 \(k + 1\) 的位置之后,我们永远可以把此时的一个选座位的方案映射回值域为 \([1, k]\) 的选座位方案。由于这是一个环,因此旋转对称,总方案要除以环长。也就是合法总方案为 \((k + 1)^{n - 1}(k + 1 - n)\),高精度一下即可。
代码
#include <iostream>
using namespace std;
const int MXDigit = 1000;
class high {
public:
short num[MXDigit];
bool positive;
int digits;
high () {
for (int i = 0; i < MXDigit; i++) {
num[i] = 0;
}
positive = true;
digits = 0;
}
inline short & operator[](int index){
return num[MXDigit - index - 1];
}
inline void operator=(int inte) {
digits = 0;
int n = MXDigit - 1;
positive = (inte >= 0);
inte *= (((int)positive - 1 << 1) + 1);
while (inte) {
num[n] = inte % 10;
inte /= 10;
n--;
digits++;
}
}
inline void getdigits() {
digits = 0;
for (; num[digits] == 0; digits++);
digits = MXDigit - digits;
}
} unit;
ostream & operator<<(ostream & out, high ha);
inline void hswap(high & ha, high & hb) {
high hc;
hc = hb;
hb = ha;
ha = hc;
}
high operator-(high ha, high hb);
bool operator>(high ha, high hb) {
if (ha.digits > hb.digits)
return true;
for (int i = 0; i < MXDigit; i++) {
if (ha.num[i] == hb.num[i]) {
continue;
}
return ha.num[i] > hb.num[i];
}
return false;
}
bool operator<(high ha, high hb) {
if (ha.digits < hb.digits)
return true;
for (int i = 0; i < MXDigit; i++) {
if (ha.num[i] == hb.num[i]) {
continue;
}
return ha.num[i] < hb.num[i];
}
return false;
}
bool operator==(high ha, high hb) {
if (ha.digits != hb.digits)
return false;
ha.getdigits();
for (int i = ha.digits - 1; i >= 0; i++) {
if (ha[i] != hb[i])
return false;
}
return (ha.positive == hb.positive);
}
inline bool operator>=(high ha, high hb) { return (!(ha < hb)); }
inline bool operator<=(high ha, high hb) { return (!(ha > hb)); }
high High(int x) {
int cnt = 0;
high ret;
ret.positive = (x >= 0);
x *= ((ret.positive - 1 << 1) + 1);
while (x) {
ret[cnt] = (x > 0 ? x % 10 : -x % 10);
cnt++, x /= 10;
ret.digits++;
}
return ret;
}
inline high operator<<(high ha, int ii) {
ha.getdigits();
for (int i = ha.digits - 1; i >= 0; i--) {
i + ii >= MXDigit ? 0 : ha[i + ii] = ha[i], ha[i] = 0;
}
ha.digits += ii;
return ha;
}
inline void operator<<=(high & ha, int ii) { ha = ha << ii; }
inline high operator>>(high ha, int ii) {
ha.getdigits();
for (int i = 0; i < ha.digits; i++) {
i + ii >= MXDigit ? 0 : ha[i] = ha[i + ii], ha[i + ii] = 0;
}
return ha;
}
inline void operator>>=(high & ha, int ii) { ha = ha >> ii; }
inline high operator-(high ha) {
ha.positive = !ha.positive;
return ha;
}
high operator+(high ha, high hb) {
ha.getdigits(), hb.getdigits();
if (ha.positive < hb.positive) {
hswap(ha, hb);
return ha - hb;
} else if (ha.positive > hb.positive) {
return ha - hb;
}
high hc;
for (int i = 0; i < max(ha.digits, hb.digits); i++) {
hc[i] += (ha[i] + hb[i]);
hc[i + 1] += hc[i] / 10;
hc[i] %= 10;
}
hc.getdigits();
hc.positive = ha.positive;
return hc;
}
inline high operator+(high h, int ii) { return h + High(ii); }
inline high operator+(int ii, high h) { return h + High(ii); }
inline void operator+=(high & ha, high hb) { ha = ha + hb; }
inline void operator+=(high & ha, int ii) { ha = ha + ii; }
high operator-(high ha, high hb) {
ha.getdigits();
if (ha.positive != hb.positive) {
return ha + (-hb);
}
high hr;
if (ha < hb) {
hswap(ha, hb);
hr.positive = false;
}
for (int i = 0; i < ha.digits; i++) {
if (ha[i] < hb[i]) {
ha[i + 1]--;
hr[i] += 10;
}
hr[i] = hr[i] + ha[i] - hb[i];
}
hr.getdigits();
hr.positive = (ha.positive == hr.positive);
return hr;
}
inline high operator-(high h, int ii) { return h - High(ii); }
inline high operator-(int ii, high h) { return High(ii) - h; }
inline void operator-=(high & ha, high hb) { ha = ha - hb; }
inline void operator-=(high & ha, int ii) { ha = ha - ii; }
high operator*(high ha, high hb) {
high hc;
ha.getdigits(), hb.getdigits();
for (int i = 0; i < ha.digits; i++) {
for (int j = 0; j < hb.digits && i + j + 1 < MXDigit; j++) {
hc[i + j] += ha[i] * hb[j];
hc[i + j + 1] += hc[i + j] / 10;
hc[i + j] %= 10;
}
}
hc.getdigits();
hc.positive = !(ha.positive ^ hb.positive);
return hc;
}
inline high operator*(high h, int ii) { return h * High(ii); }
inline high operator*(int ii, high h) { return h * High(ii); }
inline void operator*=(high & ha, high hb) { ha = ha * hb; }
inline void operator*=(high & ha, int ii) { ha = ha * ii; }
high operator/(high ha, high hb) {
ha.getdigits(), hb.getdigits();
high hc, mv, tri;
int mvs;
while (ha >= hb) {
mv = hb;
mvs = 0;
if (ha.digits - hb.digits > 1) {
mv = hb << (ha.digits - hb.digits - 1);
mvs = ha.digits - hb.digits - 1;
}
while ((mv << 1) <= ha) {
mv <<= 1;
mvs++;
}
hc[mvs] = 1;
tri = mv;
while (tri + mv <= ha) {
tri += mv;
hc[mvs]++;
}
ha -= tri;
}
hc.getdigits();
return hc;
}
inline high operator/(high h, int ii) { return h / High(ii); }
inline high operator/(int ii, high h) { return High(ii) / h; }
inline void operator/=(high & ha, high hb) { ha = ha / hb; }
inline void operator/=(high & ha, int ii) { ha = ha / ii; }
high operator%(high ha, high hb) {
high ret = ha - hb * (ha / hb);
return ret;
}
inline high operator%(high h, int ii) { return h % High(ii); }
inline high operator%(int ii, high h) { return High(ii) % h; }
inline void operator%=(high & ha, high hb) { ha = ha % hb; }
inline void operator%=(high & ha, int ii) { ha = ha % ii; }
high operator^(high ha, int ind) {
high ret;
ret = 1;
while (ind) {
if(ind & 1)
ret = ret * ha;
ind >>= 1;
ha = ha * ha;
}
ha.getdigits();
return ret;
}
ostream & operator<<(ostream & out, high ha) {
bool flag = false;
for (int i = 0; i < MXDigit; i++) {
flag |= (ha[i] != 0);
}
if (!flag) {
out << 0;
return out;
}
flag = false;
out << (ha.positive ? "" : "-");
for (int i = 0; i < MXDigit; i++) {
if (!flag) {
flag = (ha.num[i] != 0);
}
if (flag) {
out << ha.num[i];
}
}
return out;
}
int operator<<(int& x, high ha) {
bool flag = false;
for (int i = 0; i < MXDigit; i++) {
flag |= (ha[i] != 0);
}
if (!flag) {
return x = 0;
}
flag = false;
x = 0;
for (int i = 0; i < MXDigit; i++) {
if (!flag) {
flag = (ha.num[i] != 0);
}
if (flag) {
x = x * 10 + ha.num[i];
}
}
x *= ((ha.positive) ? 1 : -1);
return x;
}
istream & operator>>(istream & in, high & ha){
string str;
in >> str;
int sise = str.size();
if (str[0] == '-') {
ha.positive = 0;
}
for (int i = (str[0] == '-'); i < sise; i++) {
ha.num[MXDigit - sise + i] = str[i] - '0';
}
return in;
}
int gcd(int a, int b) { return (b ? gcd(b, a % b) : a); }
// (k + 1) ^ (n - 1) * (k + 1 - n) / (k ^ n)
int main() {
unit = 1;
int tc;
cin >> tc;
while (tc--) {
high n, k;
cin >> n >> k;
if (n > k) {
cout << "0 1\n";
continue;
}
int N;
N << n;
high a = ((k + 1) ^ (N - 1));
high x = k + 1 - n, y = k ^ N, z = y % x;
int b, c;
b << (k + 1 - n), c << z;
int t = gcd(b, c);
x /= t, y /= t;
cout << a * x << " " << y << "\n";
}
return 0;
}
T10
Max Limited Sequence
上界限制相同的拉出来,dp 即可。显然不同的上界限制间独立。
代码
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int P = 998244353;
int qpow(int x, int y = P - 2) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int n, m, q;
struct X {
int l, r, x;
} a[200005];
int dsu[200005], key[200005], kcnt;
int getf(int x) { return (dsu[x] == x ? x : (dsu[x] = getf(dsu[x]))); }
vector<int> vec[200005];
int f[200005];
signed main() {
cin >> n >> m >> q;
for (int i = 1; i <= q; i++) cin >> a[i].l >> a[i].r >> a[i].x;
sort(a + 1, a + q + 1, [](X x, X y) { return x.x < y.x; });
for (int i = 1; i <= n + 1; i++) dsu[i] = i;
int ans = 1;
for (int i = 1, j = 1; i <= q; i = j) {
kcnt = 0;
while (j <= q && a[j].x == a[i].x) ++j;
for (int k = i; k < j; k++) {
for (int x = getf(a[k].l); x <= a[k].r; x = getf(x))
key[++kcnt] = x, dsu[x] = x + 1;
}
sort(key + 1, key + kcnt + 1);
for (int k = 0; k <= kcnt + 1; k++) f[k] = 0, vec[k].clear();
for (int k = i; k < j; k++) vec[upper_bound(key + 1, key + kcnt + 1, a[k].r) - key - 1].emplace_back(lower_bound(key + 1, key + kcnt + 1, a[k].l) - key);
int lim = 0, val = qpow(a[i].x);
f[0] = qpow(a[i].x, kcnt + 1);
for (int k = 1; k <= kcnt + 1; k++) {
for (auto v : vec[k - 1]) lim = max(lim, v);
f[k] = (f[k - 1] + (lim ? P - f[lim - 1] : 0)) * val % P;
f[k] = (f[k - 1] + f[k]) % P;
}
ans = ans * (f[kcnt + 1] + P - f[kcnt]) % P;
}
for (int i = getf(1); i <= n; i = getf(i)) ans = ans * (m + 1) % P, dsu[i] = i + 1;
cout << ans << "\n";
return 0;
}
T11
数好图
朴素的 dp,我们尝试记录当前考虑了几个点,有多少点从 \(1\) 可达,能到 \(n\);从 \(1\) 不可达,能到 \(n\),从 \(1\) 可达,到不了 \(n\);以及两个都不能。这状态数爆了,需要压缩。会发现所有第三种点走到的点都是第三种点,所有能走到第二、四种点的都是第二、四种点。于是发现这三个独立了,我们分别称为第一、二(第三种点)、三(第二、四种点)类点。我们只需要先算出第一类点的个数,然后依次往里加入第二、三类点即可。第一类点的个数,我们考虑一张所有点都能从 \(1\) 到且能到 \(n\) 的 dag,其充要条件是每个点都有入度和出度。我们对着出度容斥,\(f_{i, j}\) 表示前 \(i\) 个点,钦定 \(j\) 个没有出度的方案数。转移时只需要保证每个新点都有入度。这样就算出来第一类点的个数。然后再做两遍 dp 分别加入后两种点即可。每次加入时考虑向之前已经加入的所有点的连边。总时间复杂度 \(\mathcal{O}(n^2)\)。
代码
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
int n, P;
inline void Madd(int &x, int y) { (x += y) >= P ? (x -= P) : 0; }
int qpow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int C[2005][2005];
int pw[2005];
int f[2005][2005], g[2005][2005], g1[2005], ans[2005];
signed main() {
cin >> n >> P;
for (int i = C[0][0] = pw[0] = 1; i <= 2000; i++) {
pw[i] = (pw[i - 1] * 2) % P;
for (int j = C[i][0] = 1; j <= i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
f[1][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
Madd(f[i + 1][j], f[i][j] * (pw[i - j] - 1) % P);
Madd(f[i + 1][j + 1], f[i][j] * (pw[i - j - 1] - 1) % P);
Madd(g1[i], (j & 1) ? P - f[i][j] : f[i][j]);
}
}
memset(f, 0, sizeof f);
f[1][1] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
Madd(f[i + 1][j], f[i][j] * (pw[i] - 1) % P);
Madd(f[i + 1][j + 1], f[i][j]);
}
}
g[1][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
Madd(g[i + 1][j], g[i][j]);
Madd(g[i + 1][j + 1], g[i][j] * pw[i] % P);
}
}
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= n - i; j++)
ans[i] += f[n - j - 1][i - 1] * g1[i] % P * g[n - 1][j] % P;
ans[i] %= P;
}
ans[0] = ans[2];
for (int i = 0; i <= n; i++) cout << ans[i] << " ";
cout << "\n";
return 0;
}

浙公网安备 33010602011771号