[Codeforces] combinatorics (R1600) Part.1
题单:https://codeforces.com/problemset?tags=combinatorics,1201-1600
52B. Right Triangles
原题指路:https://codeforces.com/problemset/problem/52/B
题意 ( 2 s 2\ \mathrm{s} 2 s)
给定一个 n × m ( 1 ≤ n , m ≤ 1000 ) n\times m\ \ (1\leq n,m\leq 1000) n×m (1≤n,m≤1000)的只包含字符’.‘和’*‘的字符矩阵,求其中三个顶点为’*'且直角边平行于坐标轴的直角三角形的个数.
思路
每个’*‘只能与所在行和列的其他’*‘构成直角三角形.统计第 i i i行、第 j j j列的’*‘的个数 r o w i row_i rowi和 c o l j col_j colj,则在 ( i , j ) (i,j) (i,j)处的’*'对答案的贡献为 ( r o w i − 1 ) ( c o l i − 1 ) (row_i-1)(col_i-1) (rowi−1)(coli−1),其中 − 1 -1 −1即排除自己.
注意答案可能爆int.
代码
void solve() {
int n, m; cin >> n >> m;
vector<vector<char>> a(n, vector<char>(m));
vi row(n), col(m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
if (a[i][j] == '*') row[i]++, col[j]++;
}
}
ll ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
if (a[i][j] == '*') ans += (row[i] - 1) * (col[j] - 1);
}
cout << ans;
}
int main() {
solve();
}
107B. Basketball Team
原题指路:https://codeforces.com/problemset/problem/107/B
题意
小明是包含 n n n个队员的队伍中的一员.队员来自不同专业.有编号 1 ∼ m 1\sim m 1∼m的 m m m个专业,其中人数分别为 s 1 , ⋯ , s n s_1,\cdots,s_n s1,⋯,sn,小明来自专业 h ( 1 ≤ h ≤ m ) h\ \ (1\leq h\leq m) h (1≤h≤m).求小明至少有一个同专业的队友的概率,误差不超过 1 e − 6 1\mathrm{e}-6 1e−6.若组成队伍的人数不足,输出 − 1 -1 −1.
第一行输入三个整数 n , m , h ( 1 ≤ n ≤ 100 , 1 ≤ m ≤ 1000 , 1 ≤ h ≤ m ) n,m,h\ \ (1\leq n\leq 100,1\leq m\leq 1000,1\leq h\leq m) n,m,h (1≤n≤100,1≤m≤1000,1≤h≤m).第二行输入 m m m个整数 s 1 , ⋯ , s n ( 1 ≤ s i ≤ 100 ) s_1,\cdots,s_n\ \ (1\leq s_i\leq 100) s1,⋯,sn (1≤si≤100),其中 s h s_h sh包含小明.
思路
1 1 1减不含队友的概率即可.
代码
void solve() {
int n, m, h; cin >> n >> m >> h;
vi s(m);
for (int i = 0; i < m; i++) cin >> s[i];
n--, s[--h]--; // 除掉小明
int sum = 0;
for (int i = 0; i < m; i++) sum += s[i];
if (sum < n) {
cout << -1;
return;
}
double ans = 1;
for (int i = 0; i < n; i++) ans *= 1 - (double)s[h] / (sum--);
cout << fixed << setprecision(12) << 1 - ans << endl;
}
int main() {
solve();
}
124B. Permutations
原题指路:https://codeforces.com/problemset/problem/124/B
题意
给定 n n n个 k ( 1 ≤ n , k ≤ 8 ) k\ \ (1\leq n,k\leq 8) k (1≤n,k≤8)位的整数,调整其中某些数的数码顺序,使得这些数中的最大值与最小值之差最小,初始的数和调整后的数都可以出现前导零.
思路
注意到 n , k n,k n,k很小,而 8 ! = 40320 8!=40320 8!=40320,枚举所有情况即可.
注意本题不能贪心.
代码
void solve() {
int n, k; cin >> n >> k;
vector<vi> a(n, vi(k));
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
char ch; cin >> ch;
a[i][j] = ch - '0';
}
}
vi p(k); // 排列
for (int i = 0; i < k; i++) p[i] = i;
int ans = INF;
do {
int minnum = INF, maxnum = -INF;
for (int i = 0; i < n; i++) {
int cur = 0;
for (int j = 0; j < k; j++) cur = cur * 10 + a[i][p[j]];
minnum = min(minnum, cur), maxnum = max(maxnum, cur);
}
ans = min(ans, maxnum - minnum);
} while (next_permutation(all(p)));
cout << ans;
}
int main() {
solve();
}
131C. The World is a Theatre
原题指路:https://codeforces.com/problemset/problem/131/C
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
从 n ( 4 ≤ n ≤ 30 ) n\ \ (4\leq n\leq 30) n (4≤n≤30)个男生和 m ( 1 ≤ m ≤ 30 ) m\ \ (1\leq m\leq 30) m (1≤m≤30)个女生选出 t ( 5 ≤ t ≤ n + m ) t\ \ (5\leq t\leq n+m) t (5≤t≤n+m)人组队,要求男生不少于 4 4 4人,女生不少于 1 1 1人,求方案数.
思路
设选 b o y boy boy个男生,则 a n s = ∑ b o y C n b o y C m t − b o y \displaystyle ans=\sum_{boy} C_n^{boy}C_m^{t-boy} ans=boy∑CnboyCmt−boy.
考察 b o y boy boy的取值范围,剩下的 ( t − b o y ) (t-boy) (t−boy)人选女生.
①下限:男生至少选 4 4 4人,若女生全选后剩下的人数超过 4 4 4人,则男生需选 > 4 >4 >4人,故 b o y ≥ max { 4 , t − m } boy\geq \max\{4,t-m\} boy≥max{4,t−m}.
②上限:男生至多选 ( t − 1 ) (t-1) (t−1)人,故 b o y ≤ min { n , t − 1 } boy\leq \min\{n,t-1\} boy≤min{n,t−1}.
枚举所有的 b o y boy boy,累加答案即可.
代码
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, m, t; cin >> n >> m >> t;
ll ans = 0;
for (int boy = max(4, t - m); boy <= min(n, t - 1); boy++) // 枚举男生人数
ans += C(n, boy) * C(m, t - boy);
cout << ans;
}
int main() {
solve();
}
150B. Quantity of Strings
原题指路:https://codeforces.com/problemset/problem/150/B
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
给定三个整数 n , m , k ( 1 ≤ n , m , k ≤ 2000 ) n,m,k\ \ (1\leq n,m,k\leq 2000) n,m,k (1≤n,m,k≤2000),求有多少个由 m m m种字符构成的长度为 n n n的字符串,使得其任意长度为 k k k的子串都是回文串,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
思路I
(1) k = 1 k=1 k=1或 k > n k>n k>n时, a n s = m n ans=m^n ans=mn.
(2) k = n k=n k=n时,整个字符串为回文串.
① n n n为偶数时,只需选定前 n 2 \dfrac{n}{2} 2n个字符, a n s = m n 2 ans=m^{\frac{n}{2}} ans=m2n.
② n n n为偶数时,除选定前 n 2 \dfrac{n}{2} 2n个字符外还需选定中间的字符, a n s = m n 2 + 1 ans=m^{\frac{n}{2}+1} ans=m2n+1.
综上, a n s = m ⌊ n 2 ⌋ ans=m^{\left\lfloor\frac{n}{2}\right\rfloor} ans=m⌊2n⌋.
(3) k k k为偶数时,所有字符都相同, a n s = m ans=m ans=m.
(4) k k k为奇数时,字符串形如 a b a b a b ⋯ ababab\cdots ababab⋯, a n s = m 2 ans=m^2 ans=m2.
代码I
const int MOD = 1e9 + 7;
void solve() {
int n, m, k; cin >> n >> m >> k;
if (k == 1 || k > n) cout << qpow(m, n, MOD);
else if (k == n) cout << qpow(m, (n + 1) / 2, MOD);
else if (k & 1) cout << m * m;
else cout << m;
}
int main() {
solve();
}
思路II
设字符串 s s s下标从 0 0 0开始.枚举 s s s的长度为 k k k的子串的起点 i i i,则对 ∀ j ∈ [ 0 , k − 1 ] \forall j\in[0,k-1] ∀j∈[0,k−1],有 s [ i + j ] = s [ i + k − j − 1 ] s[i+j]=s[i+k-j-1] s[i+j]=s[i+k−j−1].以下标 0 ∼ ( n − 1 ) 0\sim (n-1) 0∼(n−1)为节点,在需要相同字符的下标间连边建图,则 a n s = m c n t ans=m^{cnt} ans=mcnt,其中 c n t cnt cnt为图中的连通块的个数.
代码II
const int MAXN = 2005;
const int MOD = 1e9 + 7;
vi edges[MAXN];
bool vis[MAXN];
void dfs(int u) {
vis[u] = true;
for (auto v : edges[u])
if (!vis[v]) dfs(v);
}
void solve() {
int n, m, k; cin >> n >> m >> k;
for (int i = 0; i < n - k + 1; i++) { // 枚举长度为k的子串的起点
for (int j = 0; j < k; j++)
edges[i + j].push_back(i + k - j - 1); // 需要相同字符的下标间连边
}
int cnt = 0; // 连通块个数
for (int i = 0; i < n; i++) {
if (!vis[i]) {
cnt++;
dfs(i);
}
}
cout << qpow(m, cnt, MOD);
}
int main() {
solve();
}
思路III
同思路II,但用并查集来维护连通块.
代码III
const int MAXN = 2005;
const int MOD = 1e9 + 7;
int fa[MAXN];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) fa[x] = y;
}
void solve() {
int n, m, k; cin >> n >> m >> k;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= n - k + 1; i++) { // 枚举长度为k的子串的起点
for (int j = 0; j < k; j++) // 注意即使i从1开始枚举,j也从0开始枚举
merge(i + j, i + k - j - 1); // 合并需要相同字符的下标
}
int cnt = 0; // 连通块个数
for (int i = 1; i <= n; i++) cnt += fa[i] == i;
cout << qpow(m, cnt, MOD);
}
int main() {
solve();
}
152C. Pocket Book
原题指路:https://codeforces.com/problemset/problem/152/C
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
有编号 1 ∼ n 1\sim n 1∼n的 n n n个长度为 m m m的字符串.现有操作:选择三个整数 i , j , k ( 1 ≤ i < j ≤ n , 1 ≤ k ≤ m ) i,j,k\ \ (1\leq i<j\leq n,1\leq k\leq m) i,j,k (1≤i<j≤n,1≤k≤m),交换字符串 i i i与 j j j的长度为 k k k的前缀.问若干次操作后能得到多少种不同的名字,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
第一行输入两个整数 n , m ( 1 ≤ n , m ≤ 100 ) n,m\ \ (1\leq n,m\leq 100) n,m (1≤n,m≤100).接下来 n n n行每行输入一个长度为 m m m的且只包含小写英文字母的字符串.
思路
设所有字符串在下标 i ( 0 ≤ i ≤ m − 1 ) i\ \ (0\leq i\leq m-1) i (0≤i≤m−1)处有 c n t i cnt_i cnti种字符,则 a n s = ∏ i = 0 m − 1 c n t i \displaystyle ans=\prod_{i=0}^{m-1} cnt_i ans=i=0∏m−1cnti.
代码
const int MAXN = 105;
const int MOD = 1e9 + 7;
set<char> s[MAXN]; // 存每一列的字符
void solve() {
int n, m; cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
char ch; cin >> ch;
s[j].insert(ch);
}
}
int ans = 1;
for (int i = 0; i < m; i++) ans = (ll)ans * s[i].size() % MOD;
cout << ans;
}
int main() {
solve();
}
171B. Star
原题指路:https://codeforces.com/problemset/problem/171/B
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
如上图,给定一个整数 n ( 1 ≤ n ≤ 18257 ) n\ \ (1\leq n\leq 18257) n (1≤n≤18257),求第 n n n个Star Number.
思路
Star Number的通项 a n = 6 n ( n − 1 ) + 1 a_n=6n(n-1)+1 an=6n(n−1)+1.
代码
void solve() {
int n; cin >> n;
cout << 6 * n * (n - 1) + 1;
}
int main() {
solve();
}
204A. Little Elephant and Interval
原题指路:https://codeforces.com/problemset/problem/204/A
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
给定两个整数 l , r ( 1 ≤ l ≤ r ≤ 1 e 18 ) l,r\ \ (1\leq l\leq r\leq 1\mathrm{e}18) l,r (1≤l≤r≤1e18),问有多少个 x ∈ [ l , r ] s . t . x x\in[l,r]\ s.t.\ x x∈[l,r] s.t. x的十进制表示中最高位与最低位相同.
思路
f ( n ) f(n) f(n)表示 [ 1 , n ] [1,n] [1,n]中十进制表示的最高位与最低位相同的数的个数,则 a n s = f ( r ) − f ( l − 1 ) ans=f(r)-f(l-1) ans=f(r)−f(l−1).
下面讨论如何求 f ( n ) f(n) f(n).
①显然 [ 1 , 9 ] [1,9] [1,9]的整数都满足条件.
②对 n ≥ 10 n\geq 10 n≥10的情况,注意到个位数每 10 10 10个一循环,则 f ( n ) ≤ ⌊ n 10 ⌋ + 9 f(n)\leq \left\lfloor\dfrac{n}{10}\right\rfloor+9 f(n)≤⌊10n⌋+9.
n = 21 n=21 n=21时,上式求得的结果为 11 11 11,但事实上只有 1 , ⋯ , 9 , 11 1,\cdots,9,11 1,⋯,9,11满足条件,
这是因为当 n n n的首位 > > >末尾时末位无法取到与首位相同,故答案需 − 1 -1 −1.
代码
ll cal(ll n) {
if (n <= 9) return n;
else return n / 10 + 9 - (n / qpow(10, to_string(n).length() - 1) > n % 10);
}
void solve() {
ll l, r; cin >> l >> r;
cout << cal(r) - cal(l - 1);
}
int main() {
solve();
}
251A. Points on Line
原题指路:https://codeforces.com/problemset/problem/251/A
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
x x x轴上有 n n n个点 x 1 , ⋯ , x n x_1,\cdots,x_n x1,⋯,xn.给定一个整数 d d d,选三个相异的点,使得它们两两的距离不超过 d d d,求方案数.
第一行输入两个整数 n , d ( 1 ≤ n ≤ 1 e 5 , 1 ≤ d ≤ 1 e 9 ) n,d\ \ (1\leq n\leq 1\mathrm{e}5,1\leq d\leq 1\mathrm{e}9) n,d (1≤n≤1e5,1≤d≤1e9).第二行严格升序地输入 n n n个整数 x 1 , ⋯ , x n ( ∣ x i ∣ ≤ 1 e 9 ) x_1,\cdots,x_n\ \ (|x_i|\leq 1\mathrm{e}9) x1,⋯,xn (∣xi∣≤1e9).
思路
枚举每个起点 l l l,用双指针找到最靠右的使得 x [ r ] − x [ l ] ≤ d x[r]-x[l]\leq d x[r]−x[l]≤d的点 r r r,则该起点对答案的贡献为在中间的 ( r − l ) (r-l) (r−l)个点中任选两个,即 C r − l 2 C_{r-l}^2 Cr−l2.
代码
ll C(int n) { // 组合数C(n,2)
return (ll)n * (n - 1) / 2;
}
void solve() {
int n, d; cin >> n >> d;
vi x(n);
for (auto& xi : x) cin >> xi;
ll ans = 0;
for (int l = 0, r = 0; l < n; l++) { // 枚举起点
while (r + 1 < n && x[r + 1] - x[l] <= d) r++; // 找到最靠右的使得x[r]-x[l]≤d的点
ans += C(r - l);
}
cout << ans;
}
int main() {
solve();
}
272D. Dima and Two Sequences
原题指路:https://codeforces.com/problemset/problem/272/D
题意 ( 2 s ) (2\ \mathrm{s}) (2 s)
有两个包含 n n n个点的点列 ( a 1 , 1 ) , ⋯ , ( a n , n ) (a_1,1),\cdots,(a_n,n) (a1,1),⋯,(an,n)和 ( b 1 , 1 ) , ⋯ , ( b n , n ) (b_1,1),\cdots,(b_n,n) (b1,1),⋯,(bn,n).将这些点组成一个长度为 2 n 2n 2n的点列使得 x x x坐标不降,求方案数,答案对 m m m取模.两方案不同当且仅当至少存在一个位置的点不同.
第一行输入一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5).第二行输入 n n n个整数 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).第三行输入 n n n个整数 b 1 , ⋯ , b n ( 1 ≤ b i ≤ 1 e 9 ) b_1,\cdots,b_n\ \ (1\leq b_i\leq 1\mathrm{e}9) b1,⋯,bn (1≤bi≤1e9).第四行输入一个整数 m ( 2 ≤ m ≤ 1 e 9 + 7 ) m\ \ (2\leq m\leq 1\mathrm{e}9+7) m (2≤m≤1e9+7).
思路
s a m e x i samex_i samexi表示 x = i x=i x=i的点的个数, s a m e y i samey_i sameyi表示 x = i x=i x=i的点中有多少对 y y y相等的点, s u m = ∑ i f i \displaystyle sum=\sum_i f_i sum=i∑fi,则 a n s = ∏ i s a m e x i ! 2 s u m ans=\dfrac{\displaystyle \prod_i samex_i!}{2^{sum}} ans=2sumi∏samexi!,其中除以 2 s u m 2^{sum} 2sum是因为 x x x相等的点中至多有 2 2 2个 y y y相等的点,这两点的是无序的.
注意 2 2 2在模 m m m下的逆元未必存在,需在求阶乘的过程中约掉 2 2 2.
代码
const int MAXN = 1e5 + 5;
int MOD;
int n;
int a[MAXN], b[MAXN];
umap<int, int> samex; // samex[i]表示x=i的点的个数
umap<int, int> samey; // samey[i]表示x=i的点中有多少对y相等的点
uset<int> s; // 已计算贡献的点
int cal(int x, int y) { // x! / 2^y
int res = 1;
for (int i = 1; i <= x; i++) {
if (y && i % 2 == 0) res = (ll)res * i / 2 % MOD, y--;
else res = (ll)res * i % MOD;
}
return res;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
cin >> MOD;
for (int i = 1; i <= n; i++) {
samex[a[i]]++, samex[b[i]]++;
if (a[i] == b[i]) samey[a[i]]++;
}
int ans = 1;
for (int i = 1; i <= n; i++) {
if (!s.count(a[i])) {
ans = (ll)ans * cal(samex[a[i]], samey[a[i]]) % MOD;
s.insert(a[i]);
}
if (!s.count(b[i])) {
ans = (ll)ans * cal(samex[b[i]], samey[b[i]]) % MOD;
s.insert(b[i]);
}
}
cout << ans;
}
int main() {
solve();
}