容斥原理的基本思想是:先不考虑重叠的情况,把所有对象的数目求出,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复。
首先是一个经典的互素问题[HDOJ 4135 Co-prime]
题目大意是:给定A,B,N,求区间[A,B]中与N互素的数的个数。
思路是:先将N的所有素因子求出,然后求区间[1,A]中与N具有公因子的数的个数,然后用[1,B]减去[1,A-1]。
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<queue>//队列
#include<stack>//栈
#include<algorithm>
#include<iostream>
using namespace std;
long long p[100],k;
void getp(long long n)//求出n的所有素因子 放入p[i]
{
long long i;
k=0;
for(i=2;i*i<=n;i++)
{
if(n%i==0)
p[k++]=i;
while(n%i==0)
n/=i;
}
if(n>1)
p[k++]=n;
}
long long nop(long long m) //nop(m)得到的是区间[1,m]中所有与n具有公因子的数的个数
{
long long que[100000],i,j,sum,top,t;
que[0]=-1;//另que首项为1,可保证各项乘积的正负性,即容斥原理的思想
top=1;
for(i=0;i<k;i++) //这个循环实现了:向que[i]中放入了各个因子的乘积(包含与1的乘积)(带正负号)
{
t=top;
for(j=0;j<t;j++)
{
que[top++]=que[j]*p[i]*(-1); //这个是关键
}
}
for(i=1,sum=0;i<top;i++)
sum+=m/que[i]; //求和,得到个数
return sum;
}
int main()
{
long long a,b,n;
int t,j=1;
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld%lld",&a,&b,&n);
getp(n); //求出n的所有素因子
printf("Case #%d: %lld\n",j++,b-nop(b)-(a-1-nop(a-1))); // b-nop(b)为区间[1,b]中与n互素的数的个数,所以这个输出的式子是用[1,b]减去[1,a-1]
}
return 0;
}
然后是一个比较巧的题,Visible Trees,给一个[m,n]的矩阵(从[1,1]开始),每个点上有一颗树,站在[0,0]点看矩阵,前面的树会挡着后面的树,问此时一共可以看到多少树。
将这个题转化为数学问题,被挡住的树的坐标一定是前面某颗树坐标的倍数,也就是任意一点(a,b),如果a,b有约数,那么它一定会被挡住。所以问题转化为了求互质点对数,即用容斥原理可解决,与上题代码相似。
容斥原理的几个题大概都是这样一个思路,弄清各项间的加减关系即可。
下午尝试了一下模拟赛中的第三题,大意是,在某一次游戏中,两个人基础分为1,先选定一个任意数K(不重要),然后每一局获胜的人将分数乘K的平方,输的人将分数乘K,游戏进行N局后得到两人的成绩。问题是输入了一组成绩,问其正确性,是否是某次游戏的成绩。
题意理解后,可以得出结论:两个人的成绩的乘积一定是某个数的三次方,且两个人的成绩必须是该数的倍数。
我的代码如下:
using namespace std;
int main(){
long long int a,b,c,e,f;
int t;
scanf("%d", &t);//出现了超时情况,同学提醒我更改了输入输出方式
while(t--)
{
c=0;
scanf("%I64d %I64d", &a, &b);
c=a*b;
if(a==1&&b==1) puts("Yes");
else if(a==1||b==1) puts("No");
else{
e=(int)(pow(c,1.0/3)+0.5);//pow(a,n)求a的n次方
f=(long long)e*e*e;
if((f==c)&&(a%e==0)&&(b%e==0)) puts("Yes");
else puts("No");}
}
}
以上~