[JLoi2016]方
Posted wolfycz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JLoi2016]方相关的知识,希望对你有一定的参考价值。
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
这种题肯定考虑容斥,那么答案即为所有正方形个数-包含一个删除点的正方形个数-包含两个点的……
然后考虑一下所有正方形怎么求,主要是正方形可以斜着很烦啊……
我们考虑一下对于如下的一个正方形,我们叫它((a,b))正方形好了……
我们可以发现这个正方形占了((a+b)*(a+b))这么大一块空间,然后我们发现(a)的取值范围为([0,a+b]),所以第一部分答案我们就统计好了
然后经过删除点的正方形个数呢?经过三个和四个的二分找一下即可,并不难;经过两个主要考虑对角线的情况,同样不难,所以现在最难的便是考虑经过一个点的情况
对于一个点,和它相关的之后4个属性,(l,r,u,d)对吧
然后我们考虑一下直的,也就是边平行于坐标轴的正方形
可以发现个数即为(min(l,d)+min(d,r)+min(r,u)+min(u,l)),其他情况的正方形都是横跨了两个相邻象限的
我们考虑形如这样的正方形,那么它显然也是一个((a,b))正方形,我们令(c=a+b),然后我们考虑一下(a)的取值范围
易得(egin{cases}aleqslant r\\a<c\\a>0\\ageqslant c-l(bleqslant l)end{cases})
所以我们可以列出一个这样的柿子来计算
for (int c=2;c<=d&&c<=l+r;c++)
Ans+=min(r,c-1)-max(c-l,1)+1;
但是这样显然不够优秀,因此我们可以人工分类讨论一下……
于是我们就枚举两个断点,也就是(c=r+1),和(c=l+1)两个断点,那么在这些断点之间显然都是一次函数,那么我们就可以用等差数列求出中间的值
int calc(int l,int r,int d){
if (!l||!r||!d) return 0;
int res=0,limit=min(d,l+r),cl=1;
int v[3]={l+1,r+1,limit};
sort(v,v+3);
for (int i=0;i<3;i++){
int cr=v[i];
if (cr>limit) break;
if (cr<2||cl==cr) continue;
cl++;
int vl=min(r,cl-1)-max(1,cl-l)+1;
int vr=min(r,cr-1)-max(1,cr-l)+1;
res=(res+1ll*(vl+vr)*(cr-cl+1)/2)%Mod;
cl=cr;
}
return res;
}
然后就是考虑一些容斥,这个我就在代码里做一下注释吧……
/*problem from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
#define Fi first
#define Se second
#define MK make_pair
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline char gc(){
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline int frd(){
int x=0,f=1; char ch=gc();
for (;ch<'0'||ch>'9';ch=gc()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=gc()) x=(x<<3)+(x<<1)+ch-'0';
return x*f;
}
inline int read(){
int x=0,f=1; char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return x*f;
}
inline void print(int x){
if (x<0) putchar('-');
if (x>9) print(x/10);
putchar(x%10+'0');
}
const int N=2e3,Mod=1e8+7;
const double eps=1e-8;
pair<int,int>A[N+10];
int n,m,k,Ans;
int calc(int l,int r,int d){
if (!l||!r||!d) return 0;
int res=0,limit=min(d,l+r),cl=1;
int v[3]={l+1,r+1,limit};
sort(v,v+3);
for (int i=0;i<3;i++){
int cr=v[i];
if (cr>limit) break;
if (cr<2||cl==cr) continue;
cl++;
int vl=min(r,cl-1)-max(1,cl-l)+1;
int vr=min(r,cr-1)-max(1,cr-l)+1;
res=(res+1ll*(vl+vr)*(cr-cl+1)/2)%Mod;
cl=cr;
}
return res;
}
int work(int u,int d,int l,int r){return (calc(l,r,d)+calc(l,r,u)+calc(u,d,l)+calc(u,d,r)+min(l,d)+min(d,r)+min(r,u)+min(u,l))%Mod;}
bool ck(double x,double y){
int tx=fabs(x-(int)(x+0.5))<=eps?(int)(x+0.5):-1;
int ty=fabs(y-(int)(y+0.5))<=eps?(int)(y+0.5):-1;
return tx>=0&&tx<n&&ty>=0&&ty<m;
}
bool ck(pair<int,int>tmp){return tmp.Fi>=0&&tmp.Fi<n&&tmp.Se>=0&&tmp.Se<m;}
int main(){
n=read()+1,m=read()+1,k=read();
for (int i=1;i<=n&&i<=m;i++) Ans=(Ans+1ll*(n-i)*(m-i)%Mod*i%Mod)%Mod;
for (int i=1;i<=k;i++){
int x=read(),y=read();
Ans=(Ans-work(x,n-x-1,y,m-y-1)+Mod)%Mod;
A[i]=MK(x,y);
//这里每次减去会导致包含两个点的正方形被算了两次
}
sort(A+1,A+1+k);
int cnt3=0,cnt4=0;
for (int i=1;i<=k;i++){
for (int j=i+1;j<=k;j++){
double Mx=(A[i].Fi+A[j].Fi)/2.0,My=(A[i].Se+A[j].Se)/2.0;
double hx=A[i].Fi-Mx,hy=A[i].Se-My;
if (ck(Mx-hy,My+hx)&&ck(Mx+hy,My-hx)) Ans++;
//这里仅仅是枚举作为对角线的情况,会使得包含两个点的正方形的情况没有算全,但是包含三个点和四个点的正方形分别被考虑了一次两次
for (int p=-1;p<=1;p+=2){
double tx=A[i].Fi-A[j].Fi,ty=A[i].Se-A[j].Se;
pair<int,int>T1=MK(A[i].Fi-ty*p,A[i].Se+tx*p);
pair<int,int>T2=MK(A[j].Fi-ty*p,A[j].Se+tx*p);
if (!ck(T1)||!ck(T2)) continue;
int cnt=0; Ans++;//这里的Ans++是指完全考虑了包含两个点的情况,使得其不会被多减
if (binary_search(A+1,A+1+k,T1)) cnt++;
if (binary_search(A+1,A+1+k,T2)) cnt++;
if (cnt==1) cnt3++;
else if (cnt==2) cnt3++,cnt4++;
//这里给三个点和四个点做一下记录
}
}
}
Ans-=cnt3/2,Ans-=cnt4/4;
//包含三个点的在考虑一个点时被减去了三次,在考虑两个点时被加回来了三次,现在再次减去一次
//包含四个点的也类似
printf("%d
",(Ans%Mod+Mod)%Mod);
return 0;
}
以上是关于[JLoi2016]方的主要内容,如果未能解决你的问题,请参考以下文章