[2018.4.2集训]b-容斥-计数

题目大意

有一个长宽均为 N 的网格, 每个格子的长宽均为 1. 除了最左下角的网
格外, 其他格子中均有一个半径为 $\frac{R}{10^6}$ 的圆, 圆心在格子的正中心. 现在你站
在最左下角的格子的正中心, 求你能够看到多少个圆, 视线不能够穿过圆.

$n \leq 10^9,1 \leq R \leq 5*10^5$

题解

加强版的仪仗队......

首先,显然坐标不互质的一定看不到。
其次,对于一个圆,如果它遮住了某个圆的一半以上,那么沿被它遮住的圆与观察点构成的直线对称处一定有另一个圆恰好遮住了被遮住的圆的对称的另一半。
因此,一个圆不被遮住的条件是圆心不被遮住。

考虑点到直线的距离公式,假设被挡住的圆是$(x,y)$,挡它的圆是$(a,b)$,那么有$\frac{(ay-bx)2}{x2+y^2} \geq r^2$。

此处有一个性质:若$x,y$互质,那么一定存在$a,b$,满足$a < x,b < y,|ay-bx|=1$。可以发现,此时一定是最近的。

于是只需要$x2+y2 < \frac{1}{r^2}$即可不被挡住。

于是,可以爆枚$x,y$的约数$d$,并暴力数出合法的$(x,y)$数目,容斥一下即可。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e6+9;

int n,r;

inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0' || '9'<ch)ch=getchar();
	while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
	return x;
}

int pri[N],mu[N],ptop;
bool npri[N];

inline void init()
{
	mu[1]=1;
	for(int i=2;i<N;i++)
	{
		if(!npri[i])
		{
			pri[++ptop]=i;
			mu[i]=-1;
		}

		for(int j=1;j<=ptop && i*pri[j]<N;j++)
		{
			npri[i*pri[j]]=1;
			if(i%pri[j])
				mu[i*pri[j]]=-mu[i];
			else
			{
				mu[i*pri[j]]=0;
				break;
			}
		}
	}
}

inline ll calc(ll r,int n)
{
	ll ret=0,y=min(n,(int)(sqrt(r)+1));
	for(ll x=1;x*x<r && x<=n;x++)
	{
		while(x*x+y*y>r && y>0)y--;
		ret+=y;
	}
	return ret;
}

int main()
{
	n=read();r=read();	
	init();
	ll ans=(n==1?0:2);n--;
	ll lim=999999999999ll/r/r;
	for(ll d=1;d*d<=lim;d++)
		ans+=mu[d]*calc(lim/d/d,n/d);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2018-04-03 22:59  zltttt  阅读(295)  评论(0)    收藏  举报