BZOJ 4558 [JLoi2016] 方

216 篇文章 0 订阅
14 篇文章 0 订阅

Description

上帝说,不要圆,要方,于是便有了这道题。由于我们应该方,而且最好能够尽量方,所以上帝派我们来找正方形
上帝把我们派到了一个有N行M列的方格图上,图上一共有(N+1)×(M+1)个格点,我们需要做的就是找出这些格点形
成了多少个正方形(换句话说,正方形的四个顶点都是格点)。但是这个问题对于我们来说太难了,因为点数太多
了,所以上帝删掉了这(N+1)×(M+1)中的K个点。既然点变少了,问题也就变简单了,那么这个时候这些格点组成
了多少个正方形呢?

Input

第一行三个整数 N, M, K, 代表棋盘的行数、 列数和不能选取的顶点个数。 保证 N, M >= 1, K <=(N + 1) ×
(M + 1)。约定每行的格点从上到下依次用整数 0 到 N 编号,每列的格点依次用 0到 M 编号。接下来 K 行,每
行两个整数 x,y 代表第 x 行第 y 列的格点被删掉了。保证 0 <=x <=N<=10^6, 0 <=y<=M<=10^6,K<=2*1000且不
会出现重复的格点。

Output

 仅一行一个正整数, 代表正方形个数对 100000007( 10^8 + 7) 取模之后的值

Sample Input

2 2 4
1 0
1 2
0 1
2 1

Sample Output

1

HINT

Source

~~~~~~~~~~~~~~~~~~~~~~~~~~

容斥原理~

答案可以看成是所有正方形数-至少有1个坏点+至少有2个坏点-至少有3个坏点+至少有4个坏点。

所有正方形数:斜的正方形可以补全为正的正方形,所以一个边长为n的正方形框架里面一共有n个正方形。

至少有1个坏点:分为坏点在正方形四角上和四边上(四边上即在斜的正方形的角上),分别计算。

剩下的情况:枚举两个坏点,就可以确定出三个正方形(注意两点是对角线时要判断是不是整点正方形),判断其余两个点是不是坏点,注意这里所有枚举都是[至少有]。

取模好奇怪啊……把(ans+(m-i+1)*(n-i+1)%modd*i%modd)%modd里面i后面的%modd去掉,就从WA到A了QAQ

最后注意x3/3,x4/6(重复枚举)。

(写代码的时候重复用了变量a,居然没有报错导致WA了好久,以后要注意!)


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
#define ll long long
const ll modd=1e8+7;

int n,m,k,ni3,ni6;
ll ans,x1,x2,x3,x4;

struct node{
	int x,y;
}a[2001];

map<node,bool> id;

bool operator < (node u,node v)
{
	return u.x==v.x ? u.y<v.y:u.x<v.x;
}

int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0' && ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}

bool kk(node u)
{
	return u.x>=0 && u.x<=n && u.y>=0 && u.y<=m;
}

void add(node a,node b)
{
	map<node,bool>::iterator a1,b1;
	a1=id.find(a);b1=id.find(b);x2++;
	if(a1!=id.end() || b1!=id.end()) x3++;
	if(a1!=id.end() && b1!=id.end()) x4++;
}

void cal(int u,int v)
{
	int x1=a[u].x,x2=a[v].x,y1=a[u].y,y2=a[v].y,k1,k2;
	node a1,b1;
	a1=(node){x1+y1-y2,y1+x2-x1};
	b1=(node){x2+y1-y2,y2+x2-x1};
	if(kk(a1) && kk(b1)) add(a1,b1);
	a1=(node){2*x1-a1.x,2*y1-a1.y};
	b1=(node){2*x2-b1.x,2*y2-b1.y};
	if(kk(a1) && kk(b1)) add(a1,b1);
	if((abs(x1-x2)+abs(y1-y2))&1) return;
	k1=(x1-y1-x2+y2)/2;k2=(x1+y1-x2-y2)/2;
	a1=(node){x1-k1,y1-k2};
	b1=(node){x2+k1,y2+k2};
	if(kk(a1) && kk(b1)) add(a1,b1);
}

ll num(int u,int v,int k)
{
	ll z=min(u+v,k),now;
	if(!z) return 0;
	now=(z+3)*z/2;
	if(z>u) now-=(z-u)*(z-u+1)/2;
	if(z>v) now-=(z-v)*(z-v+1)/2;
	return now;
}

ll cal0(node u)
{
	int x=u.x,y=u.y,a=x,b=n-x,c=y,d=m-y;
	return (num(a,b,c)+num(a,b,d)+num(c,d,a)+num(c,d,b)-min(a,c)-min(a,d)-min(b,c)-min(b,d)+modd)%modd;
}

int main()
{
	n=read();m=read();k=read();
	for(int i=min(n,m);i;i--) ans=(ans+(m-i+1)*(n-i+1)%modd*i)%modd;
	for(int i=1;i<=k;i++)
	{
		a[i].x=read();a[i].y=read();id[a[i]]=1;
		ans=(ans-cal0(a[i]))%modd;
	}
	for(int i=1;i<k;i++)
	  for(int j=i+1;j<=k;j++) cal(i,j);
	ans=(ans+x2+x4/6-x3/3+modd)%modd;
	printf("%lld\n",ans);
	return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值