网上找来方便自己看,理解。
容斥原理:在计数时,必须注意无一重复,无一遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。
常用方法有两种:递归法和二进制枚举法。
递归法是利用dfs的思想进行搜索,检索每一种方案进行容斥。
二进制枚举的方法最大的好处是能够枚举出所有元素组合的不同集合。假设一个集合的元素有m个,则对于m长的二进
制数来说就有m个1或0的位置,对于每一个1就对应一个元素。
整个二进制枚举完就是所有子集,从0到2^m就行。[0, 2^m)
以hdu 1796为例:
题意:给定一个数n,数列m个数,求这小于n的数中,有多少个数满足能被这m个数中任意一个数整除。
思路:1~n之间有多少个能被x整除的数,公式为n/x,题目中要求小于n,所以(n-1)/x。
可以递归法求,需要保存中间重叠x次的最小公倍数lcm,符合题意的数有(n-1)/lcm个,利用k表示重叠的次数进
行或者加上,或者减去。
也可以用二进制枚举法,将符合条件的m个数,看作m位,每位是0或者是1,那么一共有2^m种状态,只要判断一下每
一个状态有多少个1,也就是有多少个数(重叠多少次),记为k,每一个1代表哪几个具体的数,求这几个数的最小
公倍数,然后(n-1)/lcm, 利用k的值来判断应该减去还是加上即可。
递归版本:
#includeusing namespace std; typedef long long LL; int num[25]; int ans,tot,n,m; void dfs(int pos,int pre_lcm,int k) { for(int i=pos+1;i<tot;++i) { //int lcm=(num[i]*pre_lcm)/gcd(num[i],pre_lcm); int lcm = pre_lcm/__gcd(num[i], pre_lcm)*num[i]; if(k&1) ans+=(n-1)/lcm; else ans-=(n-1)/lcm; dfs(i,lcm,k+1); } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { ans=0,tot=1; memset(num,0,sizeof(num)); for(int i=1;i<=m;++i) { int x; scanf("%d",&x); if(x>0&&x<n) num[tot++]=x; } dfs(0,1,1); printf("%d\n",ans); } return 0; }
#includeusing namespace std; int n, m, x, k, tot; int up, t, pos, lcm, ans; int num[25]; int main() { while(~scanf("%d %d", &n, &m)) { tot = 1; ans = 0; for(int i = 1; i <= m; ++i) { scanf("%d", &x); if(x > 0 && x < n) num[tot++] = x; } up = (1<<(tot-1)); for(int i = 1; i < up; ++i) { t = i, k = 0, pos = 1; lcm = 1; while(t) { if(t&1) { lcm = num[pos]/__gcd(lcm, num[pos])*lcm; ++k; } t >>= 1; ++pos; } if(k&1) ans += (n-1)/lcm; else ans -= (n-1)/lcm; } printf("%d\n", ans); } return 0; }
LL Q[100010],factor[110],num;
//Q数组存放的就是右边边各项的因子数以及正负情况,factor[]存放对应对象的数目,num为有几个对象
void Divid(LL n) //n的素因子分解,得到每项素因子的个数
{
num = 0;
for(LL i = 2; i*i <= n; ++i)
{
if(n%i==0)
{
while(n%i==0)
n /= i;
factor[num++] = i;
}
}
if(n != 1)
factor[num++] = n;
}
LL solve(LL n) //容斥定理,求
{
LL k,t,ans;
t = ans = 0;
Q[t++] = -1;
for(LL i = 0; i < num; ++i)
{
k = t;
for(LL j = 0; j < k; ++j)
Q[t++] = -1*Q[j]*factor[i];
}
//A∪B∪C = A+B+C - A∩B - B∩C - C∩A + A∩B∩C
//Q数组存放的就是A∪B∪C右边边各项的因子数以及正负情况。
for(LL i = 1; i < t; ++i)
ans += n/Q[i];
//n/Q[i]累加起来就是A∪B∪C
return ans;
}
例题:给定r,n求[1,r]内与n互素的个数有多少个?
思路:直接求解问题就是比较复杂的。所以我们还是研究这个问题是逆问题。也就是说求gcd(k,n) >= 2,在1 - n之间k有多少个 。那么我们就可以枚举n的素因子来进行求解。
int solve( int r, int n)
{
vector< int >p;
for ( int i = 2; i * i <= n; ++i){
if (n % i == 0){
p.push_back(i);
while (n % i == 0) n /= i;
}
}
if (n > 1) p.push_back(n);
int sum = 0;
for ( int S = 1; S < (1 << p.size()); ++S){
int mult = 1, bits = 0;
for ( int i = 0; i < p.size(); ++i){
if (S & (1 << i)){
++bits;
mult *= p[i];
}
}
int cur = r / mult;
if (bits % 2 == 1) sum += cur;
else sum -= cur;
}
return r - sum;
}
|