NOIP2016提高A组五校联考2】running

题意:学校的操场可以看成一个由n个格子排成的一个环形,格子按照顺时针顺序从0到n-1标号.有m个同学在跑步,最开始每个同学都在起点(即0号格子),每个同学都有个步长ai,每跑一步,每个同学都会往顺时针方向前进ai个格子.由于跑道是环形的,如果一个同学站在n-1这个格子上,如果他前进一个格子,他就会来到0.似乎有些格子永远不会被同学跑到,求这些格子的数目?(假设每个同学都跑到过0号格子)

分析:首先我们来证明一个结论:对于同学i,它的步长为ai,则编号能被gcd(ai,n)整除的格子(即编号是gcd(ai,n)的倍数的格子),他都能经过.

证明:设同学i能够经过编号为k的格子,即必定存在一个x,满足\((x*ai)\)%\(n=k\).

上式又等价于必定存在一对x和y使得\(x*ai-y*n=k\)

如果你学了扩展欧几里得算法,就应该会知道,上式要有整数解,充要条件是\(gcd(ai,n)|k\),即k能被\(gcd(ai,n)\)整除,也就是说所有编号能被gcd(ai,n)整除的格子(即编号是gcd(ai,n)的倍数的格子),同学i都能经过,证毕.

知道了这个结论后,我们还是不能愉快地做题.因为有些格子会被多个同学经过,即对于同一个k,可能存在多个i使得\(gcd(ai,n)|k\)成立,即使没有这样的情况,也可能存在多个i使得不同的\(gcd(ai,n)\)具有公因数,这种情况也会使得某些格子被多个同学经过.总而言之,我们要考虑去重.

我们考虑枚举n的所有约数,对于一个约数x,如果有一个gcd(ai,n)能被x整除(即x是gcd(ai,n)的倍数),即同学i能够经过x,则不会对答案产生贡献.

否则,对于所有的gcd(ai,n),如果有一个约数x不是它们的倍数,即所有同学都不会经过x,则所有x的倍数,也不会被经过,ans+=phi(n/x)[其中phi是欧拉函数,(phi(j)表示1到j中与j互质的数的个数)].这里我觉得是本题中最难理解的地方,好好理解一下,其实我们实际上要求的就是有多少个格子的编号k满足\(gcd(k,n)=x\),这个式子等价于\(gcd(\frac{k}{x},\frac{n}{x})=1\)(不信的话自己手写几个例子看看),即\(\frac{k}{x}\)\(\frac{n}{x}\)互质,即我们现在要求的就是有多少个k满足\(\frac{k}{x}\)\(\frac{n}{x}\)互质,又因为x是k和n的最大公约数,所以\(\frac{k}{x}\)\(\frac{n}{x}\)都是整数,所以根据欧拉函数的定义,就等价于求phi(n/x)了.

为了方便,我下面代码中的ai等价于我上面分析中的gcd(ai,n),别理解错了;

int n,m,ans,a[55];
int gcd(int x,int y){
    if(y==0)return x;
    return gcd(y,x%y);
}
int phi(int x){//求欧拉函数的模板
    int y=x;
    for(int i=2;i*i<=x;i++)
        if(x%i==0){
	    	y=y/i*(i-1);
            while(x%i==0)x/=i;
        }
    if(x>1)y=y/x*(x-1);
    return y;
}
void solve(int x){
    for(int i=1;i<=m;i++)
		if(x%a[i]==0)return;
    ans+=phi(n/x);
}
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++)
		a[i]=gcd(read(),n);
//这里a[i]直接记录的就是
//同学i能够经过所有编号是a[i]倍数的格子
    for(int i=1;i*i<=n;i++){
		if(n%i==0){//枚举n的约数i
	    	solve(i);
	    	if(i*i<n)solve(n/i);
//与i相对的n的另一个约数
		}
    }
    printf("%d\n",ans);
    return 0;
}

posted on 2019-02-15 18:49  PPXppx  阅读(133)  评论(0编辑  收藏  举报