抽屉原理&&容斥原理&&欧拉函数 (小总结)

来源:互联网 发布:行知教育集团 编辑:程序博客网 时间:2024/06/11 04:49

前一段时间学过了抽屉,容斥原理,还有欧拉函数,今天抽时间来总结一下



首先是抽屉原理,(又称鸽巢原理)

其意义是,将n个物件放回n-1个抽屉里,那么必定会有一个抽屉装有两个物件;

原理很容易理解;

在我学的过程中他的一个重要应用就是在n个数中总能找到一个或多个的数的和是m的倍数(当然n>=m),

我们可以这样来分情况:1. :n个数中存在有前缀和是m的倍数;

2. :n个数中不存在有前缀和和是m的倍数;

第一种情况就不用说了;

第二种情况我们求出这个数组的n个前缀和,那么让这n个前缀和对m进行取余可以得到n个余数,范围一定是1~m-1;

由于n>=m,那么有抽屉原理可以得到必定至少会有两个余数相等,那么这两个前缀和的差必定会被m整除;

譬如这道题:

Every year there is the same problem at Halloween: Each neighbour is only willing to give a certain total number of sweets on that day, no matter how many children call on him, so it may happen that a child will get nothing if it is too late. To avoid conflicts, the children have decided they will put all sweets together and then divide them evenly among themselves. From last year's experience of Halloween they know how many sweets they get from each neighbour. Since they care more about justice than about the number of sweets they get, they want to select a subset of the neighbours to visit, so that in sharing every child receives the same number of sweets. They will not be satisfied if they have any sweets left which cannot be divided.

Your job is to help the children and present a solution.

Input

The input contains several test cases.
The first line of each test case contains two integers c and n (1 ≤ c ≤ n ≤ 100000), the number of children and the number of neighbours, respectively. The next line contains n space separated integers a1 , ... , an (1 ≤ ai ≤ 100000 ), whereai represents the number of sweets the children get if they visit neighbour i.

The last test case is followed by two zeros.

Output

For each test case output one line with the indices of the neighbours the children should select (here, index i corresponds to neighbour i who gives a total number ofai sweets). If there is no solution where each child gets at least one sweet print "no sweets" instead. Note that if there are several solutions where each child gets at least one sweet, you may print any of them.

Sample Input
4 51 2 3 7 53 67 11 2 5 13 170 0
Sample Output
3 52 3 4


简单说一下思路:我们求出这几个前缀和,用一个vector p[ ] 二维数组来储存余数,p[0]就代表余数为0,当一个前缀和取余完后,将其下标存入对应的p[sum[i]]中;这样一检验,若p[0]大于0的话,那就说明存在m的倍数,直接输其下标就行了,如果没有的话,任意找出一个余数数组的值大于2,任取两个数一个在前一个在后,依次输出下标即可;(具体代码请点这里:传送门

第二是容斥原理:把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。

先来看一个例子:比如让你求1~n中与(2,3,5,7)不互质的个数;当n比较大的时候暴力,打表不行,我们便可以用容斥原理,先求出和(2,3,5,7)互质的个数,再拿n减去即可;那么代码该怎么写呢?假设有相交的两个集合A,B,而我们要求A,B中元素的总数,假设为p,那么

p=(A U B)-(A /\ B);

如果是三个呢:

P=(A U B U C) - (A /\ B)-(A /\ C)-(B /\ C)+(A /\ B /\ C)

在多推导几个就会发现重叠次数为奇数就加上,偶数减去;转化到我们这个问题中去,我们求出2 , 3, 5, 2*3, 2*5

3*5, 2*3*5.....的倍数的个数,因子个数为奇数的就加上,偶数减去,这样就得到了和(2,3,5,7)不互质的个数,再用总数减去就得到了问题的答案了;

下面给出代码:

#include<bits/stdc++.h>using namespace std;typedef long long ll;ll num[6]={2,3,5,7};//储存质因子; ll n;ll nop(){ll ams=0;for(ll i = 1;i < (1<<4);i++)//有4个质因子的话那么他们就会有2^4-1个组合 {ll ans=0;ll k=1;for(ll j=0;j<4;j++){if(i & (1<<j))//这是来验证哪几个质因子被选中 {  //比如当i=3,j=2时,i和j进行与运算 ans++;    // 0011 &  0010 ----> 0010,那么表示 k*=num[j];//第二个质因子'3'被选中,这样就可以得到不同数的组合 }}if(ans & 1)//当选中的数是奇数,就加上,(容斥原理---->奇加偶减) ams += n/k;elseams -= n/k;}return ams; } int main(){scanf("%lld",&n);printf("%lld\n",n-nop());//拿总数减去和2 3 5 7不互质的数 return 0;}

那么我们在扩展到一般性中,求出区间[a,b]中与n不互质的数的个数;假设范围不超过int;

把这个问题分解一下,区间[1,a-1]与n不互质的个数,同样还有[1,b]的个数,拿后者减去前者,即可得出答案;

先求第一个子问题:1.先求出n的质因子及其个数。2.用上面的方法求出互质的个数。3.减去即可;

下面附上代码:

//求区间[a,b]与n不互质的个数 //容斥原理 #include<bits/stdc++.h>using namespace std;const int MAX= 1e4 + 10;int num[MAX],j=0;int DIVDIDE(int n)//分解质因数 {int j=0;for(int i=2;i*i<n;i++){if(n%i==0){num[j++]=i;n/=i;while(n%i==0)n/=i;}}if (n > 1) num[j++] = n; return j;//n的质因子的个数 }int solve(int n,int t,int m)//求1~n与t互质的数的个数 {int ans=0;for(int i = 1;i < (1<<m); i++){int ams=0;int k=1;for(int j = 0; j < m; j++){if(i & (1<<j)){ams++;k*=num[j];}}if (ams & 1)ans += n / k;else ans -= n / k; }return ans;} int main(){int a,b,n;while(~scanf("%d %d %d",&a,&b,&n)){int l = DIVDIDE(n);//质因子的个数 int p= solve(a-1,n,l);int q= solve(b,n,l);printf("%d\n",b-(a-1)-(q-p));//解得区间[a,b]与n不互质的数的个数 }return 0;}

最后就是欧拉函数了,废话不多说了,直接看代码:

欧拉函数  一个数k的欧拉函数值等于 1 ~ k - 1 内与k互质的数的个数 欧拉函数公式 eu(k) =  k * (1 - p1) * (1 - p2) * (1 - p3)......*(1 - pk); //p1, p2 , p3 ..... pk为k的质因子 = k * ((p1 - 1) / p1) * ((p2 - 1) / p2)......((pk - 1) / pk); //p1 * p2 * p3 * .....pk = k; =(p1 - 1) * (p2 - 1) * ....(pk - 1); #include<bits/stdc++.h>using namespace std;int eu(int n){int c=n;for(int i=2;i*i<=n;i++)//求因子的话只要求到它的根号就可以了 if(n%i==0){c-=c/i;while(n%i==0) n/=i;//除去i以及i的倍数 }if(n!=1) c=c/n*(n-1);//如果n本身就是个质数,也要算进去 return c;}int main(){int n;while(~scanf("%d",&n))printf("%d\n",eu(n));return 0;}

也可以打表:(宇神的代码,^_^)

#include <cstdio>#include <cstring>#define MAX 100+1int eu[MAX];void euler(){int i, j;eu[1] = 1;//1的欧拉函数为1 看题目而定 for(i = 2; i < MAX; i++){if(!eu[i]){for(j = i; j < MAX; j += i){if(!eu[j]) eu[j] = j;eu[j] = eu[j] * (i-1) / i;}}}}int main(){euler();for(int i = 1; i < MAX; i++)printf("%d\n", eu[i]);return 0;}

关于欧拉函数还有一个原根问题。。。。设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。(其中φ(m)表示m的欧拉函数)

具体请看我的博客:这里;