[Codeforces] combinatorics (R1600) Part.1

[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  (1n,m1000)的只包含字符’.‘和’*‘的字符矩阵,求其中三个顶点为’*'且直角边平行于坐标轴的直角三角形的个数.

思路

每个’*‘只能与所在行和列的其他’*‘构成直角三角形.统计第 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) (rowi1)(coli1),其中 − 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 1m 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  (1hm).求小明至少有一个同专业的队友的概率,误差不超过 1 e − 6 1\mathrm{e}-6 1e6.若组成队伍的人数不足,输出 − 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  (1n100,1m1000,1hm).第二行输入 m m m个整数 s 1 , ⋯   , s n    ( 1 ≤ s i ≤ 100 ) s_1,\cdots,s_n\ \ (1\leq s_i\leq 100) s1,,sn  (1si100),其中 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  (1n,k8)位的整数,调整其中某些数的数码顺序,使得这些数中的最大值与最小值之差最小,初始的数和调整后的数都可以出现前导零.

思路

注意到 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  (4n30)个男生和 m    ( 1 ≤ m ≤ 30 ) m\ \ (1\leq m\leq 30) m  (1m30)个女生选出 t    ( 5 ≤ t ≤ n + m ) t\ \ (5\leq t\leq n+m) t  (5tn+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=boyCnboyCmtboy.

考察 b o y boy boy的取值范围,剩下的 ( t − b o y ) (t-boy) (tboy)人选女生.

①下限:男生至少选 4 4 4人,若女生全选后剩下的人数超过 4 4 4人,则男生需选 > 4 >4 >4人,故 b o y ≥ max ⁡ { 4 , t − m } boy\geq \max\{4,t-m\} boymax{4,tm}.

②上限:男生至多选 ( t − 1 ) (t-1) (t1)人,故 b o y ≤ min ⁡ { n , t − 1 } boy\leq \min\{n,t-1\} boymin{n,t1}.

枚举所有的 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  (1n,m,k2000),求有多少个由 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=m2n.

(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,k1],有 s [ i + j ] = s [ i + k − j − 1 ] s[i+j]=s[i+k-j-1] s[i+j]=s[i+kj1].以下标 0 ∼ ( n − 1 ) 0\sim (n-1) 0(n1)为节点,在需要相同字符的下标间连边建图,则 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 1n 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  (1i<jn,1km),交换字符串 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  (1n,m100).接下来 n n n行每行输入一个长度为 m m m的且只包含小写英文字母的字符串.

思路

设所有字符串在下标 i    ( 0 ≤ i ≤ m − 1 ) i\ \ (0\leq i\leq m-1) i  (0im1)处有 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=0m1cnti.

代码

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  (1n18257),求第 n n n个Star Number.

思路

Star Number的通项 a n = 6 n ( n − 1 ) + 1 a_n=6n(n-1)+1 an=6n(n1)+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  (1lr1e18),问有多少个 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(l1).

下面讨论如何求 f ( n ) f(n) f(n).

①显然 [ 1 , 9 ] [1,9] [1,9]的整数都满足条件.

②对 n ≥ 10 n\geq 10 n10的情况,注意到个位数每 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  (1n1e5,1d1e9).第二行严格升序地输入 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  (xi1e9).

思路

枚举每个起点 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) (rl)个点中任选两个,即 C r − l 2 C_{r-l}^2 Crl2.

代码

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  (1n1e5).第二行输入 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  (1ai1e9).第三行输入 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  (1bi1e9).第四行输入一个整数 m    ( 2 ≤ m ≤ 1 e 9 + 7 ) m\ \ (2\leq m\leq 1\mathrm{e}9+7) m  (2m1e9+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=ifi,则 a n s = ∏ i s a m e x i ! 2 s u m ans=\dfrac{\displaystyle \prod_i samex_i!}{2^{sum}} ans=2sumisamexi!,其中除以 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();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值