逆元

模为素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
constexpr int mod = 998244353;

ll qpow(ll a, ll n) {
ll res = 1;
while (n) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}

int inv(int n) {
return qpow(n, mod - 2);
} // 1/a (p是素数)

非固定模数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}

ll exgcd(ll a, ll b, ll& x, ll& y) {
if (b == 0) return x = 1, y = 0, a;
ll d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}

auto exexgcd(ll a, ll b, ll c) {
ll d = gcd(a, b);
if (c % d) return pair(-1, -1); // 无解
ll x, y;
a /= d, b /= d, c /= d;
exgcd(a, b, x, y);
x = ((x * c % b) + b * d) % b;
y = (c - a * x) / b;
return pair(x, y);
}
// 同余方程 ax == c (mod b) 的最小正整数解
// 即解不定方程 ax + by = c
// 调用liEq(a,b,c);

int mod = 123456;

ll inv(ll a) {
return liEq(a, mod, 1);
} // 1/a (mod无限制)

组合数

Cnm=n(n1)(nm+1)m(m1)1Cnm=nm+1mCnm1kCnk=nCn1k1\begin{aligned} \operatorname{C}_{n}^{m} &= \frac{ n (n-1) \cdots (n-m+1) } { m (m-1) \cdots 1} \\ \operatorname{C}_{n}^{m} &= \frac{n-m+1}{m}C_{n}^{m-1} \\ k\operatorname{C}_{n}^{k} &= n\operatorname{C}_{n-1}^{k-1} \\ \end{aligned}

杨辉三角二维递推(使用于nn 较小或模数不固定)

Cnm=Cn1m+Cn1m1C_n^m=C_{n-1}^{m}+C_{n-1}^{m-1}

1
2
3
4
5
6
7
8
ll C[N][N];
void init(int n) {
for (int i = 0; i <= n; ++i) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
}

在时间Θ(n2)\Theta(n^2)、空间Θ(n2)\Theta(n^2) 内计算C00CnnC_0^0 \sim C_n^n

预处理阶乘(万用方法,适用于nn 较大)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
constexpr int mod = 998244353;
constexpr int N = 1e6 + 8;
ll fac[N], ifac[N];

ll qpow(ll a, ll n) {
ll res = 1;
while (n) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}

ll inv(ll n) {
return qpow(n, mod - 2);
}

void init(int n) {
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % mod;
}
ifac[n] = inv(fac[n]);
for (int i = n; i > 0; i--) {
ifac[i - 1] = ifac[i] * i % mod;
}
}

ll C(int n, int m) {
if (n < m || m < 0) return 0;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
ll A(int n, int m) {
return fac[n] * ifac[n - m] % mod;
}

在时空复杂度Θ(n)\Theta(n) 内计算C00Cnn\operatorname{C}_{0}^{0} \sim \operatorname{C}_{n}^{n}

卢卡斯定理(适用于pp 较小)

对于m,nm,n 和素数ppCmni=0kCmini(modp)C_m^n\equiv\prod_{i=0}^{k}C_{m_i}^{n_i}\pmod{p},其中m=mkpk++m1p+m0, n=nkpk++n1p+n0m=m_kp^k+\cdots +m_1p+m_0,~n=n_kp^k+\cdots +n_1p+n_0mmnnpp 进制展开。

CmnCmmodpnmodpCm/pn/p(modp)C_m^n\equiv C_{m\bmod p}^{n \bmod p}\cdot C_{\lfloor m/p\rfloor}^{\lfloor n/p\rfloor}\pmod{p}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ll fac[N];
void dofac(int n, int p) {
fac[0] = fac[1] = 1;
for (int i = 2; i < n; ++i) fac[i] = fac[i - 1] * i % p;
}

ll inv[N];
ll doInv(ll a, ll p) {
a %= p;
if (a == 0) return -1;
if (a == 1) return 1;
if (inv[a]) return inv[a];
return inv[a] = (p - p / a) * doInv(p % a, p) % p;
}

ll C(ll n, ll m, ll p) {
return n < m ? 0 :
fac[n] * doInv(fac[m], p) % p * doInv(fac[n - m], p) % p;
}

ll lucas(ll n, ll m, ll p) {
return m == 0 ? 1 :
lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}

时间复杂度Θ(p)\Theta(p)

jiangly 组合数 (待修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct Comb {
int n;
std::vector<ll> _fac;
std::vector<ll> _invfac;
std::vector<ll> _inv;

Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
Comb(int n) : Comb() {
init(n);
}

void init(int m) {
if (m <= n) return;
_fac.resize(m + 1);
_invfac.resize(m + 1);
_inv.resize(m + 1);

for (int i = n + 1; i <= m; i++) {
_fac[i] = _fac[i - 1] * i;
}
_invfac[m] = _fac[m].inv();
for (int i = m; i > n; i--) {
_invfac[i - 1] = _invfac[i] * i;
_inv[i] = _invfac[i] * _fac[i - 1];
}
n = m;
}

Z fac(int m) {
if (m > n) init(2 * m);
return _fac[m];
}
Z invfac(int m) {
if (m > n) init(2 * m);
return _invfac[m];
}
Z inv(int m) {
if (m > n) init(2 * m);
return _inv[m];
}
Z binom(int n, int m) {
if (n < m || m < 0) return 0;
return fac(n) * invfac(m) * invfac(n - m);
}
} comb;

例题

2024 杭电多校 9001 - 树异或价值

[[…/contests/2024杭电多校/2024-08-16:2024“钉耙编程”中国大学生算法设计超级联赛(9)|2024-08-16:2024“钉耙编程”中国大学生算法设计超级联赛(9)]]

题意 求满足如下条件的aa 数组的数量:

  • i=1nj=1n(aiaj)×depLCA(i,j)\sum\limits_{i=1}^n \sum\limits_{j=1}^n\left(a_i \oplus a_j\right) \times \operatorname{dep}_{\operatorname{LCA}(i, j)} 最大;
  • 0ai2k10 \leqslant a_{i} \leqslant 2^{k}-1

思路 每位情况独立,现设k=1k=1

原式等价于x=1ni,jsubtree(x)(aiaj)\sum\limits_{x=1}^n \sum\limits_{i,j \in \operatorname{subtree}(x)} \left(a_i \oplus a_j\right),表示树上所有点的子树内两两之间的异或值之和的和。这就将奇怪的问题转化为 子树问题,满足树形 DP 要求。

fu,df_{u,d} 表示填完uu 的子树后0011 的数量差为dd 的方案数。由于ai{0,1}a_i \in\{0,1\},希望子树内0011 的数量尽量接近。如果子树大小为偶数,则希望数量差为00,只需考虑fu,0f_{u,0},如果子树大小为奇数,希望是±1\pm 1,且有fu,1=fu,1f_{u,1}=f_{u,-1}。因此我们 把第二维省略,设fu=fu,0f_{u}=f_{u,0}fu=fu,1f_{u}=f_{u,1}。也就是说 只考虑0\pmb0 不比1\pmb1 少的情形

uu 的子节点的子树中大小为奇数的儿子的数量有gug_{u} 个,不难发现gu\pmb {g_{u}}u\pmb u 子树大小的奇偶性相反。分类讨论:

如果gug_{u} 为奇数,为了使得0011 的数量恰好相等,

  • 如果uu 选择11,则需要在uu 的子节点vv 中选择gu12\frac{g_{u}-1}{2}11gu+12\frac{g_{u}+1}{2}00
  • 如果uu 选择00,则需要选择gu12\frac{g_{u}-1}{2}00gu+12\frac{g_{u}+1}{2}11

这种情况下,总共有hu=2Cgugu/2h_{u}=2\operatorname{C}_{g_{u}}^{g_{u} /2} 种选法。

如果gug_{u} 是偶数,为了使得0011 的数量之差为11

  • 如果uu 选择11,则需要在uu 的子节点vv 中选择gu21\frac{g_{u}}{2}-111gu2+1\frac{g_{u}}{2}+100
  • 如果uu 选择00,则需要选择gu2\frac{g_{u}}{2}11gu2\frac{g_{u}}{2}00

这种情况下,总共有hu=Cgugu/2+Cgugu/21h_{u}=\operatorname{C}_{g_{u}}^{g_{u} /2}+\operatorname{C}_{g_{u}}^{g_{u} /2-1} 种选法。

因此有fu=hufvf_{u}=h_{u}\cdot\prod f_{v}ff 数组可省略。

k=1k=1 的答案是{f1,0=f1,2nf1,1+f1,1=2f1,2n\begin{cases}f_{1,0}=f_{1} ,& 2\mid n \\f_{1,1}+f_{1,-1}=2f_{1} ,& 2\nmid n\end{cases}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int n, k;
cin >> n >> k;

vector<int> p(n + 1);
for (int u = 2; u <= n; ++u) cin >> p[u];

vector<int> g(n + 1);
Z f = 1;
for (int v = n; v; --v) {
if (g[v] % 2 == 0) {
f *= C(g[v], g[v] / 2 - 1) + C(g[v], g[v] / 2);
g[p[v]]++;
} else {
f *= 2 * C(g[v], g[v] / 2);
}
}
if (n % 2 == 1) f *= 2;

cout << qpow(f, k) << '\n';

2024 杭电多校 8005 - cats 的二分答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
constexpr int D = 60;
ll C[D + 1][D + 1];
void beforeT() {
for (int i = 0; i <= D; ++i) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]);
}
}

void eachT() {
ll l = read(), r = read();
int k = min((ll)D, read());

ll n = r - l + 1;
int m = __lg(n) + ((n & (n + 1)) == 0); // 2^m-1 <= n < 2^(m+1)-1

vector<ll> res(128);
for (int i = 0; i < D; ++i) {
res[i] += C[m][i + 1];
}

n -= (1ll << m) - 1;

int st = 0;
for (int bit = D; ~bit; --bit) {
if (n & (1ll << bit)) {
for (int i = 0; i <= bit; ++i) {
res[st + i] += C[bit][i];
}
st += 1;
}
}

ll ans = 0;
for (int i = 0; i <= k; ++i) {
ans += res[i];
}
printf("%lld\n", ans);
}

2024 杭电多校 1012 - 并

[[…/contests/2024杭电多校/2024-07-19:2024“钉耙编程”中国大学生算法设计超级联赛(1)|2024-07-19:2024“钉耙编程”中国大学生算法设计超级联赛(1)]]

平面直角坐标系上有nn 个矩形,其中第ii 个的左上角坐标为(xi,1,yi,1)(x_{i,1},y_{i,1}),右下角坐标为(xi,2,yi,2)(x_{i,2},y_{i,2})。 对于k[1,n]k \in [1,n],求解在nn 个矩形中随机选取kk 个不同的矩形,其所有覆盖部分的并集的面积的期望值。

数据范围:n2×103n \leqslant 2 \times 10^3

暴力扫描每个位置有几个矩形覆盖,先离散化,记gig_{i} 表示nn 个矩形中有cc 个矩形覆盖的面积和,kk 的答案为

i=1n(1Cnik)giCnk\sum_{i=1}^{n} \dfrac{(1-\operatorname{C}_{n-i}^{k})g_{i}}{\operatorname{C}_{n}^{k}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void eachT() {
int n = read();

vector<int> allx, ally;
allx.push_back(0);
ally.push_back(0);

vector<array<int, 4>> f(n);
for (auto& [x1, y1, x2, y2] : f) {
x1 = read(), y1 = read(), x2 = read(), y2 = read();
allx.push_back(x1);
allx.push_back(x2);
ally.push_back(y1);
ally.push_back(y2);
}
sort(allx.begin(), allx.end());
sort(ally.begin(), ally.end());
allx.erase(unique(allx.begin(), allx.end()), allx.end());
ally.erase(unique(ally.begin(), ally.end()), ally.end());

int x = allx.size(), y = ally.size();
vector w(x, vector<int>(y));
for (auto& [x1, y1, x2, y2] : f) {
x1 = lower_bound(allx.begin(), allx.end(), x1) - allx.begin();
y1 = lower_bound(ally.begin(), ally.end(), y1) - ally.begin();
x2 = lower_bound(allx.begin(), allx.end(), x2) - allx.begin();
y2 = lower_bound(ally.begin(), ally.end(), y2) - ally.begin();
++w[x1][y1];
--w[x2][y1];
--w[x1][y2];
++w[x2][y2];
}

for (int i = 1; i < x; i++) {
for (int j = 1; j < y; j++) {
w[i][j] += w[i][j - 1];
}
}

vector<Z> g(n + 1);
for (int i = 1; i < x - 1; i++) {
for (int j = 1; j < y - 1; j++) {
w[i][j] += w[i - 1][j];
g[w[i][j]] += 1ll * (allx[i + 1] - allx[i]) * (ally[j + 1] - ally[j]);
}
}

Z sum = 0;
for (int i = 1; i <= n; i++) {
sum += g[i];
}

for (int k = 1; k <= n; k++) {
Z ans = 0;
for (int i = 1; i <= n; i++) {
ans -= C[n - i][k] * g[i];
}
ans /= C[n][k];
ans += sum;
cout << ans << endl;
}
}

2024 杭电多校 5013 - 飞行棋

[[…/contests/2024杭电多校/2024-08-02:2024“钉耙编程”中国大学生算法设计超级联赛(5)#5013 - 飞行棋 (50)|2024-08-02:2024“钉耙编程”中国大学生算法设计超级联赛(5)]]

n+1n + 1 个的格子编号00nn00 号格子为起点,nn 号格子为终点。花费11 的代价等概率随机[1,n][1,n] 中的一个整数xx,向终点走xx 步,若达到终点还有剩余步数则反向行走。如果随机到了数字nn 并且未抵达终点则赠送一次(即不花费代价)等概率随机[1,n1][1,n - 1] 中整数yy 的机会,并向终点走yy 步,若达到终点还有剩余步数则反向行走。求能恰好抵达终点的代价的期望,对10000000071000000007 取模。

方法一pip_{i} 表示到达终点时代价为ii 的概率,有

pi={1n,i=1(1p1p2pi1)(1n+1n1n1),i>1p_{i}= \begin{cases} \displaystyle\frac{1}{n}, & i=1 \\ \displaystyle(1-p_{1}-p_{2}-\dots-p_{i-1})\Big(\frac{1}{n}+\frac{1}{n}\cdot\frac{1}{n-1}\Big),& i>1 \end{cases}

计算得

pi={1n,i=1(n2)i2n(n1)i2,i>1p_{i}= \begin{cases} \displaystyle \frac{1}{n}, & i=1 \\ \dfrac{(n-2)^{i-2}}{n(n-1)^{i-2}}, &i>1 \end{cases}

因此答案为

1n+1ni=2i(n2n1)i2=n1+1n\frac{1}{n}+\frac{1}{n}\sum_{i=2}^{\infty}i\cdot\Big(\dfrac{n-2}{n-1}\Big)^{i-2}=n-1+\dfrac{1}{n}

方法二00 号格时有1n\cfrac{1}{n} 的概率直接进入终点。对另外n1n\cfrac{n-1}{n} 概率发生的情况,从11 号格到n1n-1 号格花费11 的代价进入终点的期望是相同的,都是1n1\cfrac{1}{n-1},另外n2n1\cfrac{n-2}{n-1} 的概率继续停留在1n11 \sim n-1 号格内,故在这个问题上,位于1n11 \sim n-1 号格子上的状态都是等价的。所以期望花费的代价为

1+n1n(n1)=n1+1n1+\cfrac{n-1}{n} \cdot(n-1)=n-1+\dfrac{1}{n}

2024 牛客多校 4J - Zero

[[…/contests/2024牛客多校/2024-07-25:2024牛客暑期多校训练营4#*J - Zero|2024-07-25:2024牛客暑期多校训练营4]]

给出一个 01 带?? 的串,?? 等概率变成0011。一段连续相同数的贡献是区间和的pp 次幂。问期望贡献和。

fi,jf_{i,j} 表示以第ii 位为区间右端点,p=jp=j 的期望贡献和。因x0=1x^{0}=1,故初始f,0=1f_{*,0}=1,注意到(x+1)j=Cjkxk(x+1)^{j}=\sum\operatorname{C}_{j}^{k}x^{k},有转移方程

fi,j{0,si=0k=0jCjkfi1,k,si=112k=0jCjkfi1,k,si=?f_{i,j} \leftarrow \begin{cases} 0,&s_{i}=0 \\ \displaystyle\sum_{k=0}^{j} \operatorname{C}_{j}^{k}f_{i-1,k} ,&s_{i}=1 \\ \displaystyle \cfrac{1}{2}\sum_{k=0}^{j} \operatorname{C}_{j}^{k}f_{i-1,k},&s_{i}=? \end{cases}

答案为f,k\sum f_{*,k}。时间复杂度Θ(nk2)\Theta(nk^{2})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Z = ModInt<998244353>;
Z f[N][M];
void eachT() {
int n, p;
string s;
cin >> n >> p >> s;
s = ' ' + s;

Z ans = 0;
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= p; j++) {
for (int k = 0; k <= j; k++) {
f[i][j] += C[j][k] * f[i - 1][k];
}
if (s[i] == '0') f[i][j] = 0;
if (s[i] == '?') f[i][j] *= inv2;
}
f[i][0] += 1;
ans += f[i][p];
}
cout << ans << "\n";
}

2024 牛客多校 1A - A Bit Common

[[…/contests/2024牛客多校/2024-07-16:2024牛客暑期多校训练营1#A - A Bit Common|2024-07-16:2024牛客暑期多校训练营1]]

题意 求有多少长为nn 的元素是[0,2m)[0, 2^m) 的整数序列,满足存在一个非空子序列的 AND 和都是 1,答案对输入的正整数qq 取模.

思路 二进制最低位为 0 的数一定不在 AND 和是 1 的子序列里,这些数除了最低位都可以任选.

对于二进制最低位为 1 的数,如果存在一个子序列的 AND 和是 1,那么包含这个子序列的子序列的 AND 和也是 1,只要所有二进制最低位为 1 的数 AND 和是 1 就能满足条件.

记二进制最低位为 1 的数有k>0k > 0 个,这些数除了最低位的 AND 和都要是 0,也就是每一位上这些数都至少有一个是11

总方案:

k=1nCnk2(nk)(m1)(2k1)m1\sum_{k=1}^{n}\operatorname{C}_{n}^{k}2^{(n-k)(m-1)}(2^{k}-1)^{m-1}

2024 牛客多校 1B - A Bit More Common

[[…/contests/2024牛客多校/2024-07-16:2024牛客暑期多校训练营1#*B - A Bit More Common|2024-07-16:2024牛客暑期多校训练营1]]

题意 求有多少长为nn 的元素是[0,2m)[0, 2^m) 的整数序列,满足存在两个不同的非空子序列的 AND 和是11,答案对输入的正整数qq 取模.

思路 也就是说,从刚才找到的子序列中移除某个,剩下的子序列的 AND 和仍是11

考虑反面,移除任意一个,剩下的子序列的 AND 和都不是11.因此,每个数应当包含至少一个 独一无二的 0(自己的这一位是 0,其它数的这一位都是 1).

——止步于此——

难以直接计算,故递推.

fi,jf_{i,j} 表示ii 个数,共有jj 个独一无二的 0 的方案数(显然,每一个独一无二的 0 只能属于一个数).递推方程:

fi,j={1i=1i(fi,j1+fi1,j1)1<ij0i>jf_{i,j}= \begin{cases} 1 & i=1 \\ i\cdot(f_{i,j-1}+f_{i-1,j-1}) & 1<i \leqslant j \\ 0&i>j \end{cases}

反面的总方案:

k=2nCnk2(nk)(m1)j=km1Cm1j(2kk1)m1j\sum_{k=2}^{n}\operatorname{C}_{n}^{k}2^{(n-k)(m-1)} \sum_{j=k}^{m-1}\operatorname{C}_{m-1}^{j}(2^{k}-k-1)^{m-1-j}

注意k=1k=1 是个特例,此时一定无解.

因此总方案:

k=2nCnk2(nk)(m1)[(2k1)m1j=km1Cm1jfk,j(2kk1)m1j]\sum_{k=2}^{n}\operatorname{C}_{n}^{k}2^{(n-k)(m-1)} \left[ (2^{k}-1)^{m-1}-\sum_{j=k}^{m-1}\operatorname{C}_{m-1}^{j}f_{k,j}\cdot(2^{k}-k-1)^{m-1-j} \right]

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ll ans = 0;
for (int k = 2; k <= n; ++k) {
ll res = 0;
for (int j = k; j <= m - 1; ++j) {
res += C[m - 1][j] * f[k][j] % MOD
* qpow(qpow(2, k, MOD) - k - 1 + MOD, m - 1 - j, MOD) % MOD;
res %= MOD;
}
res = qpow(qpow(2, k, MOD) - 1, m - 1, MOD) % MOD - res;
res = (res % MOD + MOD) % MOD;
ans += C[n][k]
* qpow(2, (m - 1) * (n - k), MOD) % MOD
* res % MOD;
ans %= MOD;
}
printf("%lld\n", ans);

时间复杂度已经达到Θ(mnlog2N)\Theta(mn\log^{2} N),应当优化快速幂.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <cstdio>
using ll = long long;
inline ll read() {
ll S = 0, C = 0; char Y = getchar();
while (Y > 57 || Y < 48) C |= Y == 45, Y = getchar();
while (Y <= 57 && Y >= 48) S = (S << 3) + (S << 1) + Y - 48, Y = getchar();
return C ? -S : S;
}

inline ll qpow(ll S, ll Y, ll MOD) {
ll C = 1;
while (Y) {
if (Y & 1) C = C * S % MOD;
S = S * S % MOD;
Y >>= 1;
}
return C;
}

const int N = 5000 + 8;
ll C[N][N], f[N][N];
ll pow2[N];

int main() {
ll n = read(), m = read(), MOD = read();

for (int i = 0; i < N; ++i) {
C[i][0] = 1; C[i][i] = 1;
for (int j = 1; j < i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}

for (int j = 1; j <= m - 1; ++j) {
f[1][j] = 1;
}
for (int i = 2; i <= n; ++i) {
for (int j = i; j <= m - 1; ++j) {
f[i][j] = i * (f[i][j - 1] + f[i - 1][j - 1]) % MOD;
}
}

ll ans = 0;
pow2[1] = 2;
for (int k = 2; k <= n; ++k) {
pow2[k] = pow2[k - 1] * 2 % MOD;
ll res = 0;
for (int j = k; j <= m - 1; ++j) {
res += C[m - 1][j] * f[k][j] % MOD
* qpow(pow2[k] - k - 1 + MOD, m - 1 - j, MOD) % MOD;
res %= MOD;
}
res = qpow(pow2[k] - 1, m - 1, MOD) % MOD - res;
res = (res % MOD + MOD) % MOD;
ans += C[n][k]
* qpow(2, (m - 1) * (n - k), MOD) % MOD
* res % MOD;
ans %= MOD;
}
printf("%lld\n", ans);

return 0;
}

VJwin4M - Lengthening Sticks - UPSOLVE (2100)

题意 给出三根木棍的长度a, b, ca,~b,~c,可以给这三根木棍增加一些长度,长度是非负整数且总和不超过LL,求增加后这三根木棍可构成的非退化三角形的方法数。

换言之,确定数对(Δa, Δb, Δc)(\Delta{a},~\Delta{b},~\Delta{c})​ 的组数,使得下式成立:

{a+Δa<b+Δb+c+Δcb+Δb<a+Δa+c+Δcc+Δc<a+Δa+b+ΔbΔa+Δb+Δcl\begin{cases} a+\Delta{a}<b+\Delta{b}+c+\Delta{c}\\ b+\Delta{b}<a+\Delta{a}+c+\Delta{c}\\ c+\Delta{c}<a+\Delta{a}+b+\Delta{b}\\ \Delta{a}+\Delta{b}+\Delta{c} \leqslant l \end{cases}

输入只有四个数,输出只有一个数,这道题一定非!常!简!单!
而且是A题哦~~~(不告诉你是Div1的)~~

思路一 既然是数学题那当然要用数学的方法了:从反面考虑,所求方法数是总方法数减去不合法的方法数。

引理一mm 个相同的球放到nn 个不同的盒子(允许空)中的方法数为Cm+n1n1C_{m+n-1}^{n-1}。利用插板法,这是熟知的结论。

引理二k=0nCk+mm=Cn+m+1m+1\sum\limits_{k=0}^{n}C_{k+m}^{m}=C_{n+m+1}^{m+1}

下文会频繁用到这两个结果,请留意。

Step1\mathbb{Step1}\quad 总方法数为k=0LCk+22=CL+33\sum\limits_{k=0}^{L}C_{k+2}^{2}=C_{L+3}^{3},表示枚举将k[0,L]Zk\in\left[0,L\right]\cap\mathbb{Z} 个棍子分给三个盒子a,b,ca,b,c 。事实上,它与将LL 个棍子分给四个盒子等价,这四个盒子分别是a,b,ca,b,c 和未使用。

Step2\mathbb{Step2}\quad 不合法即是一边大于等于另两边之和。

若操作后aa 为最长边,即ab+ca'\geqslant b'+c'

此时枚举分给aa 的棍子i=Δai=\Delta a 的上界依旧是iLi\leqslant L,但下界需要更改为ib+cai\geqslant b+c-a(这是由于a+Δa=ab+cb+ca+\Delta a=a'\geqslant b'+c'\geqslant b+c),当然,下界不能为负数,故确切的下界为imax(0, b+ca)i\geqslant \max{(0,~b+c-a)}

剩下j=Lij=L-i 个棍子分给bbcc,即j=Δb+Δcj=\Delta b+\Delta c。首先注意,棍子数jj 不能超过a+ibca+i-b-c,这是由于a+i=ab+c=b+c+Δb+Δc=b+c+ja+i=a'\geqslant b'+c'=b+c+\Delta b+\Delta c=b+c+j。因此,0jmin(Li, a+ibc))0\leqslant j\leqslant\min{\big(L-i,~a+i-b-c)\big)}。由引理,对每个确定的ii,方法数为

j=0min(Li, a+ibc))Cj+11=Cmin(Li, a+ibc))+22\sum\limits_{j=0}^{\min{\big(L-i,~a+i-b-c)\big)}}C_{j+1}^{1}=C_{\min{\big(L-i,~a+i-b-c)\big)}+2}^{2}

i, ji,~j 求和即得此时的方法数为

i=max(0, b+ca)LCmin(Li, a+ibc))+22=?\sum\limits_{i=\max{(0,~b+c-a)}}^{L}C_{\min{\big(L-i,~a+i-b-c)\big)}+2}^{2}=?

Step3\mathbb{Step3}\quad这个结果太丑了,我们希望使用数学知识加以化简,尤其是去掉\sum。先看右边的min\rm min,它是关于ii 的两个一次函数的最小值,图像应当是一条折线。那么结果即是

\begin{align} S=C_{L+3}^{3} &-\sum\limits_{k=\max{(0,~b+c-a)}}^{L}C_{\min{\big(L-k,~k-(b+c-a)\big)}+2}^{2} \\ &-\sum\limits_{k=\max{(0,~c+a-b)}}^{L}C_{\min{\big(L-k,~k-(c+a-b)\big)}+2}^{2} \\ &-\sum\limits_{k=\max{(0,~a+b-c)}}^{L}C_{\min{\big(L-k,~k-(a+b-c)\big)}+2}^{2} \\ \end{align}

思路二 依旧是数学方法,但是从正面考虑

a, b, ca,~b,~c 的增量分别为Δa, Δb, Δc\Delta{a},~\Delta{b},~\Delta{c},并令m=cab,n=bac,p=abcm=c-a-b,n=b-a-c,p=a-b-c,则

{a+Δa<b+Δb+c+Δcb+Δb<a+Δa+c+Δcc+Δc<a+Δa+b+ΔbΔa+Δb+Δcl    {Δa+ΔbΔc>mΔa+ΔcΔb>nΔb+ΔcΔa>pΔa+Δb+Δcl\begin{cases} a+\Delta{a}<b+\Delta{b}+c+\Delta{c}\\ b+\Delta{b}<a+\Delta{a}+c+\Delta{c}\\ c+\Delta{c}<a+\Delta{a}+b+\Delta{b}\\ \Delta{a}+\Delta{b}+\Delta{c} \leqslant l \end{cases} \iff \begin{cases} \Delta{a}+\Delta{b}-\Delta{c}>m\\ \Delta{a}+\Delta{c}-\Delta{b}>n\\ \Delta{b}+\Delta{c}-\Delta{a}>p\\ \Delta{a}+\Delta{b}+\Delta{c} \leqslant l \end{cases}

w=ΔaΔb,s=Δa+Δbw=\Delta{a}-\Delta{b},s=\Delta{a}+\Delta{b},且w,sw,s 的奇偶性一致。

我们枚举ww,计算在每个ww 下有多少种方法。

那么有Δc>max(nw,p+w)\Delta{c}>\max(n-w,p+w)

因为Δa,Δb0\Delta{a},\Delta{b}\geqslant0,所以wslΔc|w|\leqslant s\leqslant l-\Delta{c}

接下来考虑把答案分成两部分来计算

第一部分是sl+m+12s\geqslant\lceil \frac{l+m+1}{2}\rceil,这个时候Δa+Δb+Δcl\Delta{a}+\Delta{b}+\Delta{c}\leqslant l的限制比Δa+ΔbΔcm+1\Delta{a}+\Delta{b}-\Delta{c}\geqslant m+1的限制更紧

因为Δa=s+w2,Δb=sw2\Delta{a}=\frac{s+w}{2},\Delta{b}=\frac{s-w}{2},所以

那么就是一个等差数列求和的形式了,首项与公差为11,项数就是lmax(nw,p+w)max(l+m+12,w))2\frac{l-\max(n-w,p+w)-\max(\lceil \frac{l+m+1}{2}\rceil,|w|))}{2}但由于奇偶需要一致,所以需要微调一下,详见代码

第二部分是ss\leqslant 刚才那个值,这个时候Δa+ΔbΔcm+1\Delta{a}+\Delta{b}-\Delta{c}\geqslant m+1的限制更紧,求答案总数的方法同上。

2024 杭电多校 1005 - 博弈

[[…/contests/2024杭电多校/2024-07-19:2024“钉耙编程”中国大学生算法设计超级联赛(1)|2024-07-19:2024“钉耙编程”中国大学生算法设计超级联赛(1)]]

题意 给定一个可重小写字符集合SS. Alice 初始时有空串AA,Bob 初始时有空串BB. 两人轮流等概率取出集合SS 中的一个字符cc,将它拼接到自己的字符串的后面,直至SS 为空,每个字符只能被取一次,Alice 先手.如果最终AA 的字典序严格大于BB,则 Alice 胜.求 Alice 胜的概率,答案对998244353998244353​ 取模.

思路 如果在某个字符iiAi>BiA_{i}>B_{i},则已有 Alice 胜;若Ai<BiA_{i}<B_{i},则 Alice 败;否则Ai=BiA_{i}=B_{i},这个字符无法判断胜负,需要继续操作,我们称这种情形为 平局.非平局时,两人互换结果,输赢翻转,概率相同,因此只需判断平局的概率.分为以下三种情形,式中p=A字符i的总数字符i的组数×组数!总数!p=\frac{\prod \operatorname{A}_{\text{字符}i\text{的总数}}^{\text{字符}i\text{的组数}}\times\text{组数}!}{\text{总数}!}

  • 若每种字母的个数都是偶数:设 Alice 和 Bob 取得的字符串完全相同的概率为pp,则答案为1p2\frac{1-p}{2}
  • 若只有一种字母的个数是奇数:对于前n12\frac{n-1}{2} 个字符,设 Alice 和 Bob 取得的字符串完全相同的概率为pp,则答案为1p2+p=1+p2\frac{1-p}{2}+p=\frac{1+p}{2}
  • 否则,答案为12\frac{1}{2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void eachT() {
int n = read();

int cnt[2] = { 0,0 };
for (int i = 1; i <= n; i++) {
char x; scanf("%c", &x);
a[i] = read();
cnt[a[i] & 1] += 1; // 桶
}

// 第三种情形
if (cnt[1] > 1) {
printf("%lld\n", inv2); // 1/2
return;
}

// 计算p(即代码中的ans)
ll ans = 1, ttcnt = 0; // 总数
for (int i = 1; i <= n; i++) {
ans = ans * fac[a[i]] % MOD * facinv[a[i] / 2] % MOD;
ttcnt += a[i];
}
ans = ans * facinv[ttcnt] % MOD * fac[ttcnt/2] % MOD;

// 利用p计算答案
// 第一种情形
if (cnt[1] == 0) printf("%lld\n", (1 - ans + MOD) % MOD * inv2 % MOD);
// 第二种情形
else printf("%lld\n", (1 + ans + MOD) % MOD * inv2 % MOD);
}

CF921D. Good Trip (1900)

题意nn 个人,mm 对朋友,第jj 对朋友的友谊值为fj (1jm)f_j~(1\leqslant j\leqslant m),进行kk 次选择,每次等概率地选择俩人,如果选择的俩人是一对朋友,他们的友谊值将增加11.求所有被选中的kk 对的友谊值总和的期望值(在增加之前).

思路 易知分母是(Cn2)k(C_n^2)^k,它表示每次选择有Cn2C_n^2 种选择方式,共选择kk 次,以下计算分子,分子是所有可能的选择方式的友谊值的总和.

因为只考虑友谊值的总和,它符合加法原理,所以可以按照朋友分开计算友谊值,再相加.

对于某对朋友,可能选择了0k0\sim k 次,如在kk 次选择中选择ii 次这对朋友,根据二项式定理,有Cki1i(Cn21)kiC_k^i\cdot 1^i\cdot\big(C_n^2-1\big)^{k-i} 种选择方式,每种选择的友谊值总和为f+(f+1)+(f+2)++(f+i1)=if+Ci2f+(f+1)+(f+2)+\dots+(f+i-1)=if+C_i^2,因此所有情况的友谊值的总和为Ckipki(if+Ci2)C_k^ip^{k-i}\big(if+C_i^2\big).(为了书写方便,这里记p=Cn2p=C_n^2,下同)

将上式对ii 求和,稍加化简,即

\begin{align} S_j &=\sum_{i=0}^{k} C_k^i (p-1)^{k-i} \big(if+C_i^2\big) \\ &= \sum_{i=1}^{k} i C_k^i (p-1)^{k-i}f + \frac12\sum_{i=2}^{k} i(i-1) C_k^i (p-1)^{k-i} \\ &= \sum_{i=1}^{k} kC_{k-1}^{i-1} (p-1)^{k-i}f + \frac12\sum_{i=2}^{k} k(k-1)C_{k-2}^{i-2} (p-1)^{k-i} \\ &= kf\sum_{i=0}^{k-1} C_{k-1}^{i} (p-1)^{k-1-i} + \frac12k(k-1)\sum_{i=0}^{k-2} C_{k-2}^{i} (p-1)^{k-2-i} \\ &= kfp^{k-1} + \frac12k(k-1)p^{k-2}, \\ \end{align}

熟知,i=0kCkipki=(p+1)k\sum\limits_{i=0}^{k} C_{k}^{i} p^{k-i} = (p+1)^{k}

由于有mm 对朋友,对这些朋友求和,

\begin{align} \sum_{j=1}^{m}S_j &=\sum_{j=1}^{m} \Big(kf_jp^{k-1} + \frac12k(k-1)p^{k-2} \Big) \\ &=kp^{k-1}\sum_{j=1}^{m}f_j + mC_k^2p^{k-2}, \\ \end{align}

即是最终的分子.

答案即是

ans=kpk1j=1mfj+mCk2pk2(Cn2)k.{\rm ans} = \frac{kp^{k-1}\sum_{j=1}^{m}f_j + mC_k^2p^{k-2}}{(C_n^2)^k}.

核心部分:

1
2
3
4
5
6
7
8
ll n = read(), m = read(), k = read();
ll p = C2(n), ans = C2(k) * m * qpow(p, k - 2), sum = 0;
while (m--) {
read(), read(); // 谁和谁是朋友不重要,只关心友谊值
sum += read();
}
ans += k * sum * qpow(p, k - 1);
print(ans * qpow(qpow(p, k), MOD - 2));

时间复杂度Θ(m+logn)\Theta(m+\log n),77ms.

评注 数据范围较小,不化简也能过.

CF925G. One-Dimensional Puzzle (2000)

题意 四种元素,按照12,3, 21,4, 32,3, 41,41\to2,3,~2\to1,4,~3\to2,3,~4\to1,4 的顺序连接,求方法数。

思路1122 必须交替排列,在中间插入若干3344。注意到,将nn 个物品放入mm 个盒子,不能留空的方法数为Cn1m1C_{n-1}^{m-1},允许不放的方法数为Cn+m1m1C_{n+m-1}^{m-1}

1
2
3
4
5
6
7
int a = read(), b = read(), c = read(), d = read();
ll ans = 0;
if (a == 0 && b == 0) ans = (c == 0) ^ (d == 0) || (c == 0) && (d == 0);
else if (a == b) ans = C(a - 1 + c, c, p) * C(b + d, d, p) + C(a + c, c, p) * C(b - 1 + d, d, p);
else if (a == b + 1) ans = C(a - 1 + c, c, p) * C(b + d, d, p);
else if (b == a + 1) ans = C(a + c, c, p) * C(b - 1 + d, d, p);
printf("%lld\n", ans % p);

CF1922B. Forming Triangles (1200)

题意 给出一些木棍,它们的长度是2ai2^{a_i},问选择三根能构成三角形的方法数.

思路 注意到i<j<k, 2i+2j<2k\forall i<j<k,~2^i+2^j<2^k,它们不能构成三角形,因此可能的情况只有等边三角形(C3(mp[i]))和腰比底长的等腰三角形(s[i-1] * C2(mp[i])).

1
2
3
4
5
6
7
8
9
memset(mp, 0, sizeof mp);
int n = read();
ll ans = 0, s = 0;
for (int i = 1; i <= n; ++i) ++mp[read()];
for (int i = 0; i <= n; ++i) {
ans += s * C2(mp[i]) + C3(mp[i]);
s += mp[i];
}
printf("%lld\n", ans);

2022 ICPC 杭州 A. Modulo Ruins the Legend

题意 给定a,b,s,ma,b,s,m,最小化(xa+yb+s)modm(xa+yb+s) \bmod m。([[…/contests/2024-09-25:The 2022 ICPC Asia Hangzhou Regional Contest|2024-09-25:The 2022 ICPC Asia Hangzhou Regional Contest]])

思路 根据裴蜀定理知存在x0,y0x_{0},y_{0},使得x0a+y0b=d=(a,b)x_{0}a+y_{0}b=d=(a,b),问题化为最小化(ud+s)modm(ud+s) \bmod m,其中ud=xa+yb, x=ux0, y=vy0ud=xa+yb,\ x=ux_{0},\ y=vy_{0}

设答案为w[0,m)w\in[0,m),则ud+s+vm=wud+s+vm=w,即ud+vm=wsud+vm=w-s,于是只能g=(m,d)wsg=(m,d)\mid w-s,因此取w=smodgw=s \bmod g

解不定方程ud+vm=wsud+vm=w-s 得到u,vu,v,再解不定方程ud=xa+ybud=xa+yb 得到x,yx,y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int n, m;
cin >> n >> m;
ll sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
}
sum %= m;
ll a = n, b = n * (n + 1) / 2;
ll d = gcd(a, b), g = gcd(d, m);
ll ans = sum % g;
auto [k, t] = exexgcd(d, m, ans - sum);
auto [x, y] = exexgcd(a, b, d * k);
cout << ans << endl;
cout << (x % m + m) % m << " " << (y % m + m) % m << endl;