2018/07/10测试T3 Draw

【题目】

题目描述:

给定笛卡尔坐标系上 n 个不重复的点。

定义一个 L 形为:
一个形如 (x,y),(x+1,y)...(x+a,y),(x,y+1)...(x,y+b) 的点集。
并且满足 a,b≥1 且 gcd(a,b)=1。

求有多少个集合的二元组 (A,B) 满足 A 和 B 都是 L 形,且 A 和 B 没有交,即A∩B=φ。其中 A 和 B 是两个集合,A 和 B可以相等。,

当 A≠B 时,我们将 (A,B) 和 (B,A) 视为不同的二元组。

输入格式:

第一行一个整数 n 。

接下来 n 行每行两个正整数 xi,yi 描述点的坐标。

输出格式:

输出一个整数,表示答案。

样例数据:

输入:

6
1 1 
1 2 
2 1 
3 3 
3 4 
4 3


输出:

2

备注:

数据规模与约定:
设坐标 xi,yi 的范围为 [1,S]。
对于 30% 的数据,S≤10。
对于 50% 的数据,S≤50。

对于 100% 的数据,S≤200,0≤n≤40000,n≤S2


【分析】

对于这道题,直接求答案是不好求的

我们不妨换个角度,先算出所有的 L 形,再减去所有相交的就是答案

对于有交点,有以下几种情况:

(红线和黑线代表两个不同的 L 形

具体的操作可以看代码


【代码】

(自认为代码还是写得简单易懂)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=40005;
const int s=200,S=205;
int n;
int a[S][S],g[S][S],p[S][S],up[S][S],right[S][S];
//g[i][j]表示的是有多少个L向右的边覆盖了格子(i,j) 
//p[i][j]表示的是中心点向上有i个点,向右有j个点,这个时候L形的数量 
int gcd(int x,int y)
{
	int z=x%y;
	while(z!=0)
	{
		x=y;
		y=z;
		z=x%y;
	}
	return y;
}
void init()
{
	memset(a,0,sizeof(a));
	memset(p,0,sizeof(p));
	memset(up,0,sizeof(up));
	memset(right,0,sizeof(right));
	int i,j;
	for(i=1;i<=s;++i)
	{
		for(j=1;j<=s;++j)
		{//这里p[i][j]根据定义是等于p[i-1][j]+p[i][j-1]的,减去p[i-1][j-1]是因为它重复计算了两次 
			if(gcd(i,j)==1)  p[i][j]=p[i-1][j]+p[i][j-1]-p[i-1][j-1]+1;
			else  p[i][j]=p[i-1][j]+p[i][j-1]-p[i-1][j-1];
		}
	}
}
long long get_L()
{
	int i,j;
	long long tot=0;
	for(i=s;i>=1;--i)                            //因为L形是上方和右方,所以从后往前枚举 
	  for(j=s;j>=1;--j)
	    if(a[i][j])
	    {
	    	up[i][j]=up[i+1][j]+1;               //这个L形往上的点的个数增加 
	    	right[i][j]=right[i][j+1]+1;         //这个L形往右的点的个数增加 
	    }
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
	    {
	    	up[i][j]--;                          //使两边(除中心点)上的up,right值为0,方便后面的操作 
	    	right[i][j]--;
	    }
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
	      tot+=p[up[i][j]][right[i][j]];         //这个时候,两边上会有1个值为0,p数组中值为0,因此只会统计中心点 
	return tot*(tot-1);                          //顺序问题 
}
long long remove1()
{
	int i,j;
	long long tot=0;
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
	      tot+=p[up[i][j]][right[i][j]]*(p[up[i][j]][right[i][j]]-1);
	return tot;
}
long long remove2()
{
	int i,j,k;
	long long ans,res,tot=0;
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
	      for(k=i+1;a[k][j];++k)                 //枚举每个L形向右的点 
	    	g[k][j]+=p[up[i][j]][right[i][j]]-p[k-i-1][right[i][j]];
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
	    {
	    	for(k=j+1;a[i][k];++k)
	    	{
	    		ans=p[up[i][j]][right[i][j]]-p[up[i][j]][k-j-1];
	    		tot+=2*ans*g[i][k];
	    	}
	    }
	for(i=1;i<=s;++i)
	  for(j=1;j<=s;++j)
	    if(a[i][j])
		{
			for(k=i+1;a[k][j]&&k<=up[i][j]+i;++k)
			{
				ans=p[up[i][j]][right[i][j]]-p[k-i-1][right[i][j]];
				res=p[up[k][j]][right[k][j]];
				tot+=2*ans*res;
			}
			for(k=j+1;a[i][k]&&k<=j+right[i][j];++k)
			{
				ans=p[up[i][j]][right[i][j]]-p[up[i][j]][k-j-1];
				res=p[up[i][k]][right[i][k]];
				tot+=2*ans*res;
			}
		}
	return tot;
}
int main()
{
//	freopen("draw.in","r",stdin);
//	freopen("draw.out","w",stdout);
	int x,y,i,j;
	long long s1,s2,s3;
	scanf("%d",&n); 
	init();                                      //初始化+预处理 
	for(i=1;i<=n;++i)
	{
		scanf("%d%d",&x,&y);
		a[x][y]=1;
	}
	s1=get_L();                                  //处理L形并计算总个数 
	s2=remove1();                                //第一种情况
	s3=remove2();                                //第二、三、四种情况 
	printf("%lld",s1-s2-s3);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_dreams/article/details/80999550