[Codeforces] combinatorics (R1600) Part.4
题单:https://codeforces.com/problemset?tags=combinatorics,1201-1600
735C. Tennis Championship
原题指路:https://codeforces.com/problemset/problem/735/C
题意 ( 2 s 2\ \mathrm{s} 2 s)
求一个包含 n ( 2 ≤ n ≤ 1 e 18 ) n\ \ (2\leq n\leq 1\mathrm{e}18) n (2≤n≤1e18)个节点的、每个节点的两棵子树高度之差不超过 1 1 1的二叉树的最大高度(高度从 0 0 0开始).
思路
f ( h ) f(h) f(h)表示高度为 h h h的满足条件的二叉树所包含的最小节点数.考虑其根节点的两棵子树,它们的高度只能为 ( h − 1 ) (h-1) (h−1)或 ( h − 2 ) (h-2) (h−2),则 f ( h ) = f ( h − 1 ) + f ( h − 2 ) f(h)=f(h-1)+f(h-2) f(h)=f(h−1)+f(h−2).
设 S n = ∑ i = 1 n f ( i ) \displaystyle S_n=\sum_{i=1}^n f(i) Sn=i=1∑nf(i),问题转化为求最小的 a n s s . t . S a n s ≥ n ans\ s.t.\ S_{ans}\geq n ans s.t. Sans≥n.
代码
vl fib, ans;
void init() {
fib.push_back(1), fib.push_back(1);
for (int i = 2; fib.back() < 1e18; i++)
fib.push_back(fib[fib.size() - 2] + fib[fib.size() - 1]);
ans.push_back(0);
for (int i = 0; ans.back() < 1e18; i++)
ans.push_back(fib[i] + ans.back());
}
void solve() {
init();
ll n; cin >> n;
cout << lower_bound(all(ans), n) - ans.begin() - 1;
}
int main() {
solve();
}
804B. Minimum number of steps
原题指路:https://codeforces.com/problemset/problem/804/B
题意
给定一个长度不超过 1 e 6 1\mathrm{e}6 1e6的只包含字符’a’和’b’的字符串 s s s.现有操作:选择 s s s中的一个子串"ab",将其替换为"bba",无法操作时停止.求最小操作次数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
思路
注意到操作一次会减少一对’a’在’b’之前的字符对,同时使得’b’的个数翻倍,则最终字符串形如 b ⋯ b a ⋯ a b\cdots ba\cdots a b⋯ba⋯a.
注意到前面的子串"ab"操作一次后产生的’a’可能会与后面的’b’构成子串"ab",故可从后往前考虑,遇到一个字符’b’时令其 c n t + + cnt++ cnt++;遇到一个字符’a’时 a n s + = c n t , c n t ∗ = 2 ans+=cnt,cnt*=2 ans+=cnt,cnt∗=2.
代码
const int MOD = 1e9 + 7;
void solve() {
string s; cin >> s;
reverse(all(s));
int cnt = 0; // 字符'b'的个数
int ans=0;
for (auto ch : s) {
if (ch == 'b') cnt++;
else {
ans = ((ll)ans + cnt) % MOD;
cnt = (ll)cnt * 2 % MOD;
}
}
cout << ans;
}
int main() {
solve();
}
817B. Makes And The Product
原题指路:https://codeforces.com/problemset/problem/817/B
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
给定一个长度为 n ( 3 ≤ n ≤ 1 e 5 ) n\ \ (3\leq n\leq 1\mathrm{e}5) n (3≤n≤1e5)的整数序列 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (1≤ai≤1e9).求有多少对 ( i , j , k ) s . t . i < j < k , a i ⋅ a j ⋅ a k (i,j,k)\ s.t.\ i<j<k,a_i\cdot a_j\cdot a_k (i,j,k) s.t. i<j<k,ai⋅aj⋅ak最小.
思路
显然应贪心地选 a [ ] a[] a[]中前三小的元素,不妨设为 x , y , z x,y,z x,y,z,它们的出现次数分别为 c n t 1 , c n t 2 , c n t 3 cnt_1,cnt_2,cnt_3 cnt1,cnt2,cnt3.
① x ≠ y ≠ z x\neq y\neq z x=y=z时, a n s = c n t 1 ⋅ c n t 2 ⋅ c n t 3 ans=cnt_1\cdot cnt_2\cdot cnt_3 ans=cnt1⋅cnt2⋅cnt3.
② x = y = z x=y=z x=y=z时, a n s = C c n t 1 3 ans=C_{cnt_1}^3 ans=Ccnt13.
③ x = y ≠ z x=y\neq z x=y=z时, a n s = C c n t 1 2 ⋅ c n t 3 ans=C_{cnt_1}^2\cdot cnt_3 ans=Ccnt12⋅cnt3.
④ x ≠ y = z x\neq y=z x=y=z时, a n s = c n t 1 ⋅ C c n t 2 2 ans=cnt_1\cdot C_{cnt_2}^2 ans=cnt1⋅Ccnt22.
代码
map<int, int> cnt;
ll C(int n, int m) { // 组合数C(n,m)
ll res = 1;
for (int i = n - m + 1; i <= n; i++) res = res * i / (i - n + m);
return res;
}
void solve() {
int n; cin >> n;
vi a(n);
for (auto& ai : a) {
cin >> ai;
cnt[ai]++;
}
sort(all(a));
if (a[0] != a[1] && a[1] != a[2]) cout << (ll)cnt[a[0]] * cnt[a[1]] * cnt[a[2]];
else if (a[0] == a[1] && a[1] == a[2]) cout << C(cnt[a[0]], 3);
else if (a[0] == a[1] && a[1] != a[2]) cout << C(cnt[a[0]], 2) * cnt[a[2]];
else cout << C(cnt[a[1]], 2) * cnt[a[0]];
}
int main() {
solve();
}
840A. Leha and Function
原题指路:https://codeforces.com/problemset/problem/840/A
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
考察从 n n n个元素 [ 1 , ⋯ , n ] [1,\cdots,n] [1,⋯,n]中选 k k k个元素构成一个子集,定义 f ( n , k ) f(n,k) f(n,k)表示所有子集中最小元素的期望.
给定两长度为 n ( 1 ≤ n ≤ 2 e 5 ) n\ \ (1\leq n\leq 2\mathrm{e}5) n (1≤n≤2e5)的序列 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an和 b 1 , ⋯ , b n ( 1 ≤ a i , b i ≤ 1 e 9 ) b_1,\cdots,b_n\ \ (1\leq a_i,b_i\leq 1\mathrm{e}9) b1,⋯,bn (1≤ai,bi≤1e9).将序列 a [ ] a[] a[]重排为序列 a ′ [ ] s . t . ∑ i = 1 n f ( a i , b i ) a'[]\ s.t.\ \displaystyle\sum_{i=1}^n f(a_i,b_i) a′[] s.t. i=1∑nf(ai,bi)最大,输出任一满足条件的序列 a ′ [ ] a'[] a′[].
思路
从 n n n个元素中选 k k k个元素的方案数为 C n k C_n^k Cnk.枚举 i i i,为使得其是子集中最小的元素,应在比它大的 ( n − i ) (n-i) (n−i)个元素中选 ( k − 1 ) (k-1) (k−1)个元素,故 f ( n , k ) = ∑ i = 1 n − k + 1 i ⋅ C n − i k − 1 C n k f(n,k)=\dfrac{\displaystyle\sum_{i=1}^{n-k+1} i\cdot C_{n-i}^{k-1}}{C_n^k} f(n,k)=Cnki=1∑n−k+1i⋅Cn−ik−1.
注意到 f ( n , k ) f ( n , k − 1 ) = k k + 1 \dfrac{f(n,k)}{f(n,k-1)}=\dfrac{k}{k+1} f(n,k−1)f(n,k)=k+1k,则 f ( n , k ) = n + 1 k + 1 f(n,k)=\dfrac{n+1}{k+1} f(n,k)=k+1n+1.为使得 f ( n , k ) f(n,k) f(n,k)尽量大,应让较大的 n n n与较小的 k k k配对.
将序列 a [ ] a[] a[]降序排列、 b [ ] b[] b[]升序排列后配对即可.
代码
void solve() {
int n; cin >> n;
vi a(n);
vii b(n);
for (auto& ai : a) cin >> ai;
for (int i = 0; i < n; i++) {
cin >> b[i].first;
b[i].second = i;
}
sort(all(a), greater<int>()), sort(all(b));
vi ans(n);
for (int i = 0; i < n; i++) ans[b[i].second] = a[i];
for (int i = 0; i < n; i++) cout << ans[i] << " \n"[i == n - 1];
}
int main() {
solve();
}
844B. Rectangles
原题指路:https://codeforces.com/problemset/problem/844/B
题意
给定一个 n × m ( 1 ≤ n , m ≤ 50 ) n\times m\ \ (1\leq n,m\leq 50) n×m (1≤n,m≤50)的 0 − 1 0-1 0−1矩阵.将元素分为若干个非空集合,使得集合内的数字相等,且任意两个集合中的元素同行或同列,求集合数.
思路
注意到符合要求的集合只能包含一行或一列,统计每行、每列的 0 0 0、 1 1 1的个数,不妨设第 i i i行的 0 0 0的个数为 r o w 0 row_0 row0,则该行对答案的贡献为 2 r o w 0 − 1 2^{row_0}-1 2row0−1,即幂集减去空集.对每行和每列的答案求和,因单个元素在行和列都被计算了一次,答案减去 n m nm nm即可.
代码
void solve() {
int n, m; cin >> n >> m;
vi row0(n), row1(n); // 每行0、1的个数
vi col0(m), col1(m); // 每列0、1的个数
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int a; cin >> a;
if (a == 0) row0[i]++, col0[j]++;
else row1[i]++, col1[j]++;
}
}
ll ans = -n * m;
for (int i = 0; i < n; i++) ans += ((ll)1 << row0[i]) - 1 + ((ll)1 << row1[i]) - 1;
for (int i = 0; i < m; i++) ans += ((ll)1 << col0[i]) - 1 + ((ll)1 << col1[i]) - 1;
cout << ans;
}
int main() {
solve();
}
888D. Almost Identity Permutations
原题指路:https://codeforces.com/problemset/problem/888/D
题意 ( 2 s 2\ \mathrm{s} 2 s)
给定两整数 n , k n,k n,k.称一个 1 ∼ n 1\sim n 1∼n排列 p p p是好的,如果存在至少 ( n − k ) (n-k) (n−k)个下标 i ( 1 ≤ i ≤ n ) s . t . p i = i i\ \ (1\leq i\leq n)\ s.t.\ p_i=i i (1≤i≤n) s.t. pi=i.
给定两整数 n , k ( 1 ≤ k ≤ 4 ≤ n ≤ 1000 ) n,k\ \ (1\leq k\leq 4\leq n\leq 1000) n,k (1≤k≤4≤n≤1000),求好的排列的个数.
思路
考虑如何求恰有 m ( 1 ≤ m ≤ k ) m\ \ (1\leq m\leq k) m (1≤m≤k)个下标 i ( 1 ≤ i ≤ n ) s . t . p i ≠ i i\ \ (1\leq i\leq n)\ s.t.\ p_i\neq i i (1≤i≤n) s.t. pi=i的排列的个数.先从 n n n个数中选出 m m m个数作为满足 p i ≠ i p_i\neq i pi=i的数的下标,有 C n m C_n^m Cnm种情况.将这 m m m个数错排,共 f ( m ) f(m) f(m)种情况,其中 f ( n ) f(n) f(n)表示 1 ∼ n 1\sim n 1∼n的排列的错排数,可递推求得.故 a n s = ∑ m = 1 k C n m f ( m ) \displaystyle ans=\sum_{m=1}^k C_n^m f(m) ans=m=1∑kCnmf(m).当 m = 1 m=1 m=1时,错排数 f ( m ) = 0 f(m)=0 f(m)=0,但事实上 k = 1 k=1 k=1时存在一种满足的方案 1 , ⋯ , n 1,\cdots,n 1,⋯,n,故 a n s = 1 + ∑ m = 2 k C n m f ( m ) \displaystyle ans=1+\sum_{m=2}^k C_n^m f(m) ans=1+m=2∑kCnmf(m).
代码
const int MAXN = 1005;
ll C[MAXN][MAXN]; // C[i][j]表示组合数C(i,j)
int f[MAXN]; // 错排数
void init() { // 预处理C[][]和f[]
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j <= i; j++) {
if (!j) C[i][j] = 1; // 初始条件
else C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
f[1] = 0, f[2] = 1;
for (int i = 3; i < 5; i++) f[i] = (i - 1) * (f[i - 1] + f[i - 2]);
}
void solve() {
init();
int n, k; cin >> n >> k;
ll ans = 1; // 特判i=1;
for (int i = 2; i <= k; i++) ans += C[n][i] * f[i];
cout << ans;
}
int main() {
solve();
}
978E. Bus Video System
原题指路:https://codeforces.com/problemset/problem/978/E
题意
某公交系统会记录每一站车上人数的变化.若上一站车上有 x x x人,该站车上有 y y y人,则记录 ( y − x ) (y-x) (y−x).有 n n n个公交站,在每个公交站记录的数据分别是 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an.设车的最大容量为 w w w人(无需考虑司机),问初始时车上的人数有几种可能,若无合法方案,输出 0 0 0.
第一行输入两个整数 n , w ( 1 ≤ n ≤ 1000 , 1 ≤ w ≤ 1 e 9 ) n,w\ \ (1\leq n\leq 1000,1\leq w\leq 1\mathrm{e}9) n,w (1≤n≤1000,1≤w≤1e9).第二行输入 n n n个整数 a 1 , ⋯ , a n ( − 1 e 6 ≤ a i ≤ 1 e 6 ) a_1,\cdots,a_n\ \ (-1\mathrm{e}6\leq a_i\leq 1\mathrm{e}6) a1,⋯,an (−1e6≤ai≤1e6).
思路
设初始时车上的人数为 x x x.设 a [ ] a[] a[]的前缀和为 p r e [ ] , m a x d = max 1 ≤ i ≤ n p r e i , m i n d = min 1 ≤ i ≤ n p r e i pre[],\displaystyle maxd=\max_{1\leq i\leq n}pre_i,mind=\min_{1\leq i\leq n}pre_i pre[],maxd=1≤i≤nmaxprei,mind=1≤i≤nminprei,则全程车上的最大人数为 x + m a x d x+maxd x+maxd,最小人数为 x + m i n d x+mind x+mind.
由车的最大容量和人数非负知: x + m a x d ≤ w , x + m i n d ≥ 0 x+maxd\leq w,x+mind\geq 0 x+maxd≤w,x+mind≥0,该不等式组的整数解个数 a n s = max { w − m a x d + m i n d + 1 } ans=\max\{w-maxd+mind+1\} ans=max{w−maxd+mind+1}.
注意 m a x d maxd maxd和 m i n d mind mind不能初始化为 − I N F -INF −INF和 I N F INF INF,因为车上的人数最小为 0 0 0,都初始化为 0 0 0即可.
代码
void solve() {
int n, w; cin >> n >> w;
int pre = 0; // a[]的前缀和
int maxd = 0, mind = 0; // 当前pre[]的最大值、最小值,注意初始化
while (n--) {
int a; cin >> a;
pre += a;
maxd = max(maxd, pre), mind = min(mind, pre);
}
cout << max(0, w - maxd + mind + 1);
}
int main() {
solve();
}
1007A. Reorder the Array
原题指路:https://codeforces.com/problemset/problem/1007/A
题意 ( 2 s 2\ \mathrm{s} 2 s)
给定一个长度为 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5)的整数序列 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (1≤ai≤1e9),将其重新排列为序列 b [ ] b[] b[].求一个序列 b [ ] s . t . ∑ i = 1 n [ b i > a i ] b[]\ s.t.\ \displaystyle \sum_{i=1}^n [b_i>a_i] b[] s.t. i=1∑n[bi>ai]最大,输出最大值.
思路
将 a [ ] a[] a[]升序排列,对每个 a i a_i ai,考察其后有多少个 a j ( i > j ) s . t . a j > a i a_j\ \ (i>j)\ s.t.\ a_j>a_i aj (i>j) s.t. aj>ai,答案即为所有 a i a_i ai的结果之和,用双指针即可做到 O ( n ) O(n) O(n).
代码
void solve() {
int n; cin >> n;
vi a(n);
for (auto& ai : a) cin >> ai;
sort(all(a));
int ans = 0;
for (int i = 0, j = 1; i < n - 1 && j < n; i++, j++) {
if (a[j] > a[i]) ans++;
else i--;
}
cout << ans;
}
int main() {
solve();
}
1081C. Colorful Bricks
原题指路:https://codeforces.com/problemset/problem/1081/C
题意 ( 2 s 2\ \mathrm{s} 2 s)
用 m m m种颜色给排成一行的 n n n块砖染色,要求恰有 k k k块砖与其左边的砖的颜色不同,求染色方案数,答案对 998244353 998244353 998244353取模.
第一行输入三个整数 n , m , k ( 1 ≤ n , m ≤ 2000 , 0 ≤ k ≤ n − 1 ) n,m,k\ \ (1\leq n,m\leq 2000,0\leq k\leq n-1) n,m,k (1≤n,m≤2000,0≤k≤n−1).
思路I
d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i块砖中有 j j j块砖与其左边的砖的颜色不同的方案数,则 a n s = d p [ n ] [ k ] ans=dp[n][k] ans=dp[n][k].
根据第 i i i块砖与第 ( i − 1 ) (i-1) (i−1)块砖颜色是否相同分类,状态转移方程 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + ( m − 1 ) ⋅ d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j]+(m-1)\cdot dp[i-1][j-1] dp[i][j]=dp[i−1][j]+(m−1)⋅dp[i−1][j−1].初始条件 d p [ i ] [ 0 ] = m dp[i][0]=m dp[i][0]=m,即 1 ∼ i 1\sim i 1∼i号砖都染同种颜色的方案数.
代码I
const int MAXN = 2005;
const int MOD = 998244353;
int n, m, k; // 砖数、颜色数、不同数
int dp[MAXN][MAXN]; // dp[i][j]表示前i块砖中有j块砖与其左边的砖的颜色不同的方案数
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
dp[i][0] = m;
for (int j = 1; j <= k; j++)
dp[i][j] = ((ll)dp[i - 1][j] + (ll)(m - 1) * dp[i - 1][j - 1]) % MOD;
}
cout << dp[n][k];
}
int main() {
solve();
}
思路II
将一段连续的相同颜色的砖视为一个部分,将序列分为 ( k + 1 ) (k+1) (k+1)个部分,使得相邻颜色不同只出现在相邻两部分的交界处.第一个部分颜色有 m m m种选择,后面每个部分与前面的部分颜色不同,故有 m ( m − 1 ) k m(m-1)^k m(m−1)k种情况.
在长度为 n n n的序列的 ( n − 1 ) (n-1) (n−1)个空中选 k k k个作为相邻两部分的分界点,有 C n − 1 k C_{n-1}^k Cn−1k种情况,故 a n s = C n − 1 k m ( m − 1 ) k ans=C_{n-1}^k m(m-1)^k ans=Cn−1km(m−1)k.
代码II
const int MAXN = 2005;
const int MOD = 998244353;
int n, m, k; // 砖数、颜色数、不同数
int C[MAXN][MAXN]; // C[i][j]表示组合数C(i,j)
void init() {
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j <= i; j++) {
if (!j) C[i][j] = 1; // 初始条件
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
}
void solve() {
init();
cin >> n >> m >> k;
cout << (ll)C[n - 1][k] * m % MOD * qpow(m - 1, k, MOD) % MOD;
}
int main() {
solve();
}
1084C. The Fair Nut and String
原题指路:https://codeforces.com/problemset/problem/1084/C
题意
给定一个长度不超过 1 e 5 1\mathrm{e}5 1e5的只包含小写英文字母的字符串 s s s,求满足如下条件的严格递增的整数序列 p 1 , ⋯ , p k p_1,\cdots,p_k p1,⋯,pk的个数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模:① s p i = a ( 1 ≤ i ≤ k ) s_{p_i}=a\ \ (1\leq i\leq k) spi=a (1≤i≤k);②对 ∀ i ∈ [ 1 , k ) , ∃ j s . t . p i < j < p i + 1 \forall i\in [1,k),\exists j\ s.t.\ p_i<j<p_{i+1} ∀i∈[1,k),∃j s.t. pi<j<pi+1且 s j = b s_j=b sj=b.
思路
显然 s s s中非’a’、'b’的字符不会对答案产生贡献.
显然满足条件的下标 p i p_i pi和 j j j对应的子列有形式$abab\cdots $.对每个’a’,找到其左边第一个’b’,则以该’b’结尾的所有序列后接上当前的’a’仍是合法序列.
用一个变量 l a s t last last记录上一次遇到’b’时求得的答案,初始时 l a s t = 0 last=0 last=0.①遇到’a’时, a n s + = l a s t + 1 ans+=last+1 ans+=last+1;②遇到’b’时, l a s t = a n s last=ans last=ans.
代码
const int MOD = 1e9 + 7;
void solve() {
string s; cin >> s;
int ans = 0;
int last = 0; // 上一次遇到'b'时求得的答案
for (auto ch : s) {
if (ch == 'a') ans = (ans + last + 1) % MOD;
else if (ch == 'b') last = ans;
}
cout << ans;
}
int main() {
solve();
}