容斥定理

传送门

求指定区间内与n互素的数的个数:

 给出整数n和r。求区间[1;r]中与n互素的数的个数。

 去解决它的逆问题,求不与n互素的数的个数。

 考虑n的所有素因子pi(i=1…k)

在[1;r]中有多少数能被pi整除呢?它就是:

       

然而,如果我们单纯将所有结果相加,会得到错误答案。有些数可能被统计多次(被好几个素因子整除)。所以,我们要运用容斥原理来解决。

 我们可以用2^k的算法求出所有的pi组合,然后计算每种组合的pi乘积,通过容斥原理来对结果进行加减处理。

    既然要找的是1-r中与n互素的数字的个数,找互素的数字不好找,那么可以思考逆向求解,找与n有共同因子的数字的个数,这样用总的数字减掉找到的有共同因子的数字的个数,就是我们要求的与n互素的数字的个数。

那么求与n有共同因子的数字该怎么求呢??

  1. 首先,先找出2-n以内n的基本因子。
  2. 然后再找1-r中与n有相同基本因子的数字,计数。
  3. 找出所有与n有共同因子的数字个数之后
  4. 用总数减去与n有共同因子的数字个数
  5. 就是我们要求的数字的个数
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int n,r;
int solve(int n,int r)
{
   vector<int>p;
   for(int i=2;i*i<=n;++i)
   {
       if(n%i==0)
	   {
	      p.push_back(i);
		  while(n%i==0)
		  {
		     n=n/i;		  	
		  }

	   } 	
   }
	   if(n>1)
	      p.push_back(n); 

	   int sum=0;
	   for(int msk=1;msk<(1<<p.size());++msk)
	   {
	      int mult=1,bits=0;
		   
		  for(int i=0;i<(int)p.size();++i)
		  {
		     if(msk & (1<<i)) //目的在于找到所有的因子
		     {
		     	++bits;
		     	mult*=p[i];
			 }		  	
		  }

		  int cur=r/mult; //求个数
		  if(bits%2==1)
		  {
		  	  sum+=cur;
		  }
		  else
		      sum-=cur;
	   }

	   return r-sum;	
} 
int main()
{
	while(scanf("%d%d",&n,&r)!=EOF)
	{
	cout<<solve(n,r)<<endl;		
	}
	return 0;
}

HDU 1796 How many integers can you find

题意:求在1-(n-1)中,能够被给定序列中的任意数字整除的数字的个数。

注意:

  1. 用  long long
  2. 当输入是0的时候就不再考虑

 上一个自己写的错误代码

/*
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
typedef long long int ll;
ll q[20];
ll solve(ll n,ll r)
{
	vector<ll>p;
	for(int i=0;i<n;i++)
	{
		p.push_back(q[i]);
	}
	//for(int i=0;i<p.size();i++)
	//printf("%d ",p[i]);
    ll sum=0;
	for(int msk=1;msk<(1<<p.size());msk++)
	{
	  ll bits=0,mult=1;
	  for(int i=0;i<p.size();i++)
	  {
	  	  if(msk & (1<<i))
	  	  {
	  	      bits++;
			  mult*=p[i];	
		  }
	  }
	  ll cur=r/mult;
	  if(bits%2==1)
	  {
	  	sum+=cur; 
	  }
	  else{
	  	sum-=cur;
	  }
	}
 return sum;
} 
int main()
{
	ll n,m;
	while(cin>>n>>m){
		ll sum=0;
	for(int i=0;i<m;i++)
	{
		ll x;
		cin>>x;
		if(x>0)
		 q[i]=x;
	}
	sum+=solve(m,n-1);
	cout<<sum<<endl;		
	}
	return 0;
} */

上一个别人写的正确代码

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>
using namespace std;
#define INT long long
const int N = 12;
INT p[N];
INT gcd(INT a, INT b)
{
    if(b == 0) return a;
    return gcd(b, a % b);
}
 
INT lcm(INT a, INT b)
{
    return a * b / gcd(a, b);
}
 
INT solve(INT n, INT m)
{
    INT ans = 0;
    for(INT i = 1; i < (1 << m); i ++){
        INT cnt = 0, tmp = 1;
        for(INT j = 0; j < m; j ++){
            if(i & (1 << j)){
                cnt ++;
                tmp = lcm(tmp, p[j]);
            }
 
        }
        if(cnt % 2) ans += n / tmp;
        else ans -= n / tmp;
    }
    return ans;
}
 
int main()
{
    INT n, m;
    while(cin >> n >> m){
        INT t = 0, x;
        for(INT i = 0; i < m; i ++){
            cin >> x;
            if(x > 0) p[t ++] = x;
        }
        m = t;
        cout << solve(n - 1, m) << endl;
    }
    return 0;
}


T^TOJ 2332 电灯泡

V_Dragon有n栈电灯泡,编号为1-n,每个灯泡都有一个开关。那么问题来了

  1. 所有灯泡初始时为不亮的

  2. V_Dragon分别进行三次操作

  3. 每次操作他都选一个质数x,将编号为x和x的整数倍的灯泡的开关都拨动一下(如果灯为亮,那么拨动以后灯为不亮,如果灯不亮,拨动以后变为亮)

求最后亮着的灯的数量

 

 

起初给出三个灯都是不亮的,当按一次之后,灯就全部亮了。按两次之后,灯就熄灭了。这样就可以总结出一个规律:按奇数次的时候,灯是亮的;按偶数次的时候,灯是熄灭的。

下面用一个容斥定理的例子来解释这个问题。

设S=(A∪B∪C)=A +B+C-(A∩B+A∩C+B∩C)+(A∩B∩C)

  1. A +B+C,把A、B、C中所有的数字都加了一遍,包括A∩B、A∩C、B∩C 重叠的部分。
  2. 加A的时候,加了一次A∩B,加B的时候,加了一次A∩B,这样就加了两次A∩B,A∩B这一部分是熄灭的,不是我们最终想要的结果,因此,要把加上的这两次都减去。
  3. A∩C、B∩C同理,加上的两次都要减去。
  4. 加A的时候,加了一次A∩B∩C,加B、C的时候也是加了一次A∩B∩C,这样就是加了3次A∩B∩C。
  5. 减A∩B的时候,减了两次,A∩B∩C也是减了两次,同理减A∩C、B∩C的时候,都是各减两次,这样就是减了6次
  6. A∩B∩C是按了奇数次的,我们应当保留一次,所以 +3-6=-3,最后应当加上四倍的A∩B∩C。

上图中的空白部分就是要求的数字。


#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
typedef long long int ll;
ll t,n,a,b,c;
int main()
{
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld",&n);
		scanf("%lld%lld%lld",&a,&b,&c);
		ll sum=n/a+n/b+n/c-2*((n/(a*b))+(n/(a*c))+(n/(b*c)))+4*(n/(a*b*c));
		cout<<sum<<endl;
//由于题目要求,数字之间是互质的,所以任意两个数字之间的最大公因数就是1
//如果没有互质这一个前提条件的话
// (n*gcd(a,b)/a*b) n是要乘上一个最大公因数
//int 时还可以这样写 int sum=n/a+n/b+n/c-2*(n/a/b+n/a/c+n/b/c)+4*(n/a/b/c); 
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值