[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]方的主要内容,如果未能解决你的问题,请参考以下文章

bzoj4558[JLoi2016]方 容斥+count

[JLoi2016]方

[JLOI2016]方[题解]

bzoj4558: [JLoi2016]方

BZOJ4558: [JLoi2016]方

BZOJ4558: [JLoi2016]方