ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

容斥原理学习笔记

2022-07-15 01:02:35  阅读:173  来源: 互联网

标签:dbinom int sum 容斥 笔记 template 原理 include void


引入

用一个简单的例子来引入容斥:

这个例子可以说是非常经典,一共有三种活动 \(\{1,2,3\}\) 。
同时你可以查询 \(f(S)\) 会返回参加了 \(S\) 集合中活动的人的个数。
现在的问题是求至少有多少个人参加了活动。

我记得这个是班上高一上集合时的作业最后一题。
看上去非常奇怪,但是可以发现如果根据题目意思画一个韦恩图就是非常简单的事了。
这里省略画图的过程,答案还是比较显然的:

\[f(\{1\})+f(\{2\})+f(\{3\})-f(\{1,2\})-f(\{1,3\})-f(\{2,3\})+f(\{1,2,3\}) \]

前置知识

二项式定理

可以发现对于 \((a+b)^n\) 的各个位的系数和杨辉三角有很大的关系。
杨辉三角满足 \(T_{i,j}=T_{i-1,j}+T_{i-1,j-1}\) 。
我们记 \(\dbinom{n}{m}\) 表示组合数形如 \(C_n^m\) 。
容易得到:

\[\dbinom{n}{m}=C_n^m=\frac{n!}{m!(n-m)!} \]

直接给出二项式定理的结论,证明的话直接归纳。

\[(a+b) ^n = \sum_{i=0}^n\dbinom{n}{i}a^ib^{n-i} \]

组合数的相关公式

其实上过小学的人都知道,组合数和杨辉三角也是对应的。

\[\dbinom{n}{i}=\dbinom{n-1}{i}+\dbinom{n-1}{i-1} \]

如果要证明的话可以直接化简一下式子。

\[\begin{split} \dbinom{n}{i}&=\frac{n!}{i!(n-i)!}\\ \dbinom{n-1}{i}&=\frac{(n-1)!}{i!(n-1-i)!}=\frac{n!(n-i)}{i!n(n-i)!}\\ \dbinom{n-1}{i-1}&=\frac{(n-1)!}{(i-1)!(n-i)!}=\frac{n!i}{i!n(n-i)!} \end{split} \]

式子化简到这一步也就是非常显然的了。

奇奇怪怪的知识(可能用不到)

在莫比乌斯反演中好像比较吃香,就是求和符号可以随便换位置。
具体就不举例子了。

正文

最简单的容斥还是之前引入的那个问题。
我们仍然记 \(f(S)\) 为参加了 \(S\) 这个集合中所有活动的人的个数。
但是我们这次要从公式化的角度去得到答案,我们记 \(U\) 表示所有的元素,则:

\[Ans=\sum_{S\subseteq U}f(S)(-1)^{|s|+1} \]

还是非常简单的,小学生画一个韦恩图都可以搞明白。

比较牛逼的容斥方法

首先我们令之前的 \(f(s)\) 查询操作改名为 \(q(s)\) 。
用 \(g(x)\) 表示一个在结合 \(x\) 中的元素提供的贡献,\(f(x)\) 表示容斥系数。
他们的关系可以这样表示:

\[g(n)=\sum_{i=1}^n\dbinom{n}{i}f(i) \]

接下来我们可以发现,\(f(x)\) 可以通过递推得到。

\[g(n+1)=\sum_{i=1}^{n+1}\dbinom{n+1}{i}f(i)\\ \]

因为当 \(i=n+1\) 时 \(\binom{n+1}{i}=\binom{n+1}{n+1}=1\) ,所以可以直接提取出 \(f(n+1)\) 。

\[g(n+1)=\sum_{i=1}^{n}\dbinom{n+1}{i}f(i)+f(n+1)\\ f(n+1)=g(n+1)-\sum_{i=1}^{n}\dbinom{n+1}{i}f(i) \]

直接搞就可以啦。
听说还有多项式求逆的 \(O(n\log n)\) 的做法,但是我不会,先咕一下。

一些应用或者入门题

1. 还是之前引入时的题目,可以进行一些转化。

直接套用公式就可以了,这里就再把式子放一下,不再进行解释:

\[Ans=\sum_{S\subseteq U}f(S)(-1)^{|s|+1} \]

2.错排问题

错排就是对于 \(1\sim n\) 的排列 \(a_i\) ,对于所有的 \(i\) ,满足 \(a_i\neq i\) 。
直接算贡献不太好算,计算出不合法也就是存在 \(a_i=i\) 的情况。
我们假设有 \(num\) 个位置满足 \(a_i=i\) ,那么剩下来的怎么拍都可以,也就是 \((n-num)!\) 。
我们用 \(f(n)\) 表示长度为 \(n\) 的排列的错排个数。
很容易列出下面的这个式子:

\[f(n) = \sum_{i=0}^n(-1)^i\dbinom{n}{i}(n-i)! \]

这个式子是非常显然的,现在来看看可不可以搞出一个递推式。

\[\begin{split} f(n+1)&=\sum_{i=0}^{n+1}(-1)^i\dbinom{n+1}{i}(n+1-i)!\\ &=\sum_{i=0}^n(-1)^i\dbinom{n+1}{i}(n+1-i)!+(-1)^{n+1}\dbinom{n+1}{n+1}(n+1-n-1)!\\ &=(-1)^{n+1}+\sum_{i=0}^n(-1)^i\dbinom{n+1}{i}(n+1-i)! \end{split} \]

前面非常清楚了,现在来看后面的那一个部分:

\[\begin{split} Num&=\sum_{i=0}^n(-1)^i\dbinom{n+1}{i}(n+1-i)!\\ &=\sum_{i=0}^n(-1)^i\dbinom{n}{i}\times(n+1)\times(n-i)!\times(n+1)\\ &=(n+1)^2\sum_{i=0}^n(-1)^i\dbinom{n}{i}(n-i)! \end{split} \]

可以发现现在和之前的式子

\[f(n) = \sum_{i=0}^n(-1)^i\dbinom{n}{i}(n-i)! \]

挂上了关系。
所以就可以得到递推式:

\[f(n)=(-1)^n+nf(n-1) \]

3.Hdu 上的一道简单题 Co-prime

题目描述:给你 \(l\) ,\(r\) 和 \(n\) ,问 \([l,r]\) 中有多少个数和 \(n\) 互质。
其中 \(l,r\leq 10^{15}\) ,\(n\leq10^9\) 。

很容易发现,直接计算不太好算,所以采用类似前缀和的方法。
我们记录 \(f(i)\) 表示 \([1,i]\) 中和 \(n\) 互质数的个数,那么答案是 \(f(r)-f(l-1)\) 。
现在来考虑怎么计算出 \(f(i)\) 。
发现最坏的情况下 \(n\) 只会有 \(10\) 个质因数组成,所以我们先对 \(n\) 质因数分解。
然后直接 \(pnum!\) 暴力枚举每一种可能性就可以了。
直接容斥,具体细节直接看代码:

#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>

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

using std::abs;
using std::pair;
using std::string;
using std::make_pair;

#define int long long

template <class T> void read(T &a);
template <class T> void write(T x);

template <class T, class ...rest> void read(T &a, rest &...x);
template <class T, class ...rest> void write(T x, rest ...a);

int T, l, r, n, num[50], tot, ok[50], ans1, ans2;

inline void get_prime(int n) {
  for (int i = 2; i * i <= n; i++) {
    if (n % i == 0) {
      num[++tot] = i;
      while (n % i == 0) n /= i;
    }
  } 
  if (n != 1) num[++tot] = n;
  return ;
}

inline void dfs(int k) {
  if (k > tot) {
    int chose = 0, tmp = -1, mul = 1;
    for (int i = 1; i <= tot; i++) {
      chose += ok[i];
      mul = mul * (ok[i] == 1 ? num[i] : 1);
    }
    if (chose % 2 == 0) tmp = 1;
    ans1 += (l - 1) / mul * tmp;
    ans2 += r / mul * tmp;
    return ;
  }
  ok[k] = 1; dfs(k + 1);
  ok[k] = 0; dfs(k + 1);
}

signed main(void) {
  read(T);
  for (int test = 1; test <= T; test ++) {
    tot = ans1 = ans2 = 0;
    memset(num, 0, sizeof(num));
    read(l, r, n); get_prime(n);
    dfs(1);
    printf("Case #%d: ", test);
    write(ans2 - ans1); Enter;
  }
  return 0;
}
/*
2
1 10 2
3 15 5
*/

template <class T> void read(T &a) {
  int s = 0, t = 1;
  char c = getchar();
  while (!isdigit(c) && c != '-') c = getchar();
  if (c == '-') c = getchar(), t = -1;
  while (isdigit(c)) s = s * 10 + c - '0', c = getchar();
  a = s * t;
}
template <class T> void write(T x) {
  if (x == 0) putchar('0');
  if (x < 0) putchar('-'), x = -x;
  int top = 0, sta[50] = {0};
  while (x) sta[++top] = x % 10, x /= 10;
  while (top) putchar(sta[top] + '0'), top --;
  return ;
}

template <class T, class ...rest> void read(T &a, rest &...x) {
  read(a); read(x...);
}
template <class T, class ...rest> void write(T x, rest ...a) {
  write(x); quad; write(a...);
}

标签:dbinom,int,sum,容斥,笔记,template,原理,include,void
来源: https://www.cnblogs.com/Oier-GGG/p/16472395.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有