Loading

组合计数入门题瞎做

luogu P5135 painting

题目链接

很入门的一道题,没有什么难度。
显然的,按照 \(op\) 进行分类讨论:

  • \(op=1\) ,答案是 \(\dbinom{n}{m}\) 。原因很简单,先随机得到所在列然后排个序就可以了。
  • \(op=0\) ,答案是 \(\dbinom{n+m-1}{m}\) 。也不难,考虑上述情况的解法,这里不再累述。

此时因为 \(T\)\(m\) 都很小,所以直接暴力算组合数就可以了。
所以代码难度是小学组的,复杂度 \(O(Tm)\) ,代码不放了。

[HNOI2011] 卡农

题目链接

好牛逼的一道题…… 可能是因为我太菜了……
发现对于两个不同的曲子的定义没有什么用处,先求出总的结果然后除以 \(m!\) 就可以了。

然后我就不会了,最后只能看一眼题解……
考虑现在把题目进行转化:我们把音阶看成一个集合

\[S=\{1,2,3,\cdots ,n-1,n\} \]

然后我们从这个集合 \(S\) 中抽取 \(m\) 个不为空的子集作为题目中提到的音乐段。
我们定义取出的音乐段的集合为 \(S_2\) ,那么根据题目的描述,有两个个非常显然的限制:

  • 对于任意 \(s_1,s_2\in S_2\) ,满足 \(s_1\neq s_2\)
  • 所有集合 \(S_2\) 中的片段,不同音阶的出现次数应该为偶数。

考虑用 \(\text{dp}\) 解决问题,设状态 \(F_i\) 表示到了第 \(i\) 个子集的方案数。
对于 \(F_i\) 的转移,尝试用总共可以取到的方案数减去不满足上述性质的方案数,最后得到答案。

先来看总共可以取到的方案数:
我们可以把每一个音阶看成是二进制上的一个位置,用 \(0\)\(1\) 表述它出现的次数。
这样的话我们可以发现,上文出现次数为偶数的限制可以转换成集合内每个片段所代表的的二进制异或和为 \(0\)
考虑到现在的第 \(i\) 位,它完全取决于之前 \(i-1\) 位所填的方法,对于每一种 \(i-1\) 的排列都有不同的异或和。
所以现在得出总的方案数是 \(A_{2^n-1}^{i-1}\) 就不那么困难了。

接下来考虑怎么去掉方案中的空集 —— 空集显然是不合法的。
对于一个片段 \(i\) ,如果它是空集,那么前 \(i-1\) 个片段一定可以构成一个完整的曲子。
所以这一部分的答案就是 \(F_{i-1}\)

再来看任意两个集合不一样的限制:
我们假设第 \(i\) 个和第 \(j\) 个集合时一模一样的,那么我们把 \(i\)\(j\) 删掉一定可以构成一个合法的音乐,这个方案是 \(F_{i-2}\)
其中对于所谓的 \(j\) ,一共有 \(i-1\) 种取值,所以一定有一项是 \(i-1\)
最后,对于子集 \(i\) ,一共有 \(2^n-1-(i-2)\) 种取值,原因显然。
最后可以得到转移方程 :

\[F_i=A_{2^n-1}^{i-1}-F_{i-1}-F_{i-2}\times(i-1)\times(2^n-i+1) \]

点击查看代码
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define Enter putchar('\n')
#define quad putchar(' ')

#define int long long
const int mod = 1e8 + 7;
const int N = 2e6 + 5;

int n, m, F[N], two[N], mul, A[N];

inline int power(int, int);

signed main(void) {
  std::cin >> n >> m;
  two[0] = A[0] = 1;
  for (int i = 1; i <= n + m; i++) two[i] = two[i - 1] * 2 % mod;
  int maxn = two[n] - 1 + mod;
  for (int i = 1; i <= m; i++) A[i] = A[i - 1] * (maxn - i + 1 + mod) % mod; 
  F[0] = 1;
  int mul = 1, pmul = 1;
  for (int i = 2; i <= m; i++) {
    mul = mul * i % mod;
    F[i] = A[i - 1] - F[i - 1] - F[i - 2] * (i - 1) % mod * (maxn - i + 2 + mod) % mod;
    F[i] = (F[i] % mod + mod) % mod;
  }
  // std::cout << F[m] << std::endl;
  int ans = F[m] * power(mul, mod - 2) % mod;
  printf("%lld\n", (ans % mod + mod) % mod);
  return 0;
}

inline int power(int a, int n) {
  int ret = 1;
  while (n) {
    if (n & 1) ret = ret * a % mod;
    a = a * a % mod; n /= 2;
  }
  return ret;
} 

CF1437F Emotional Fishermen

题目链接

发现自己组合计数的水平真的跟吃屎一样,还是做一点简单的题目吧……

发现最后的方案数和初始的 \(a_i\) 没有任何的关系,所以先按照 \(a_i\) 从小到大排序。

我们假设最后的合法排列是这样的:

\[P_1,P_2,P_3,\cdots ,P_{n-1},P_n \]

那么一定满足 \(2P_j<P_i\ (j\leq i)\) ,同时 \(2a_j<P_i\) 的数都可以放在 \(P_i\) 的右边。
我们令 \(limit_i\) 表示最大的 \(j\) 满足 \(2a_j<a_i\)

考虑 \(\text{DP}\) ,想到一个很显然的状态: \(F_i\) 表示当前最大值是 \(a_i\) 时的方案数。
注意到此时一定选取了 \(limit_i+1\) 个数。
可以得到 :

\[F_i=\sum_{j=1}^{limit_i}F_j\times A_{limit_i-limit_j-1}^{n-limit_j-2} \]

原因也很简单,考虑一共剩下多少个数以及有多少个位置可以填就可以了。
代码小学难度,不放了。

posted @ 2022-08-13 23:10  Aonynation  阅读(38)  评论(3编辑  收藏  举报