• 「题解」:[BZOJ4558]方


    问题: 方

    时间限制: 2 Sec  内存限制: 256 MB

    题面


    题目描述

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

    输入格式

    第一行三个整数 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且不 会出现重复的格点。

    输出格式

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

    样例输入

    2 2 4

    1 0

    1 2

    0 1

    2 1

    样例输出

    1

    题解


    此题有毒!卡了我一整天。

    从昨天下午一点多开始一直到今天的十一点,我才A掉了这到毒瘤题……

    细节超多。

    我们直接统计没有坏点的正方形个数显然不现实。暴力……没敢想。最起码$O(n^4)$的吧……

    所以需要容斥原理。

    我们处理出至少有零个坏点的正方形个数sum0,至少有一个坏点的正方形个数sum1,以此类推sum2、sum3和sum4的含义。

    然后考虑怎么处理。

    处理sum0:

    我们通过观察得出这样的性质:

    1.每一个斜正方形必定有唯一确定的包含它的最小的正正方形。

    2.每一个边长为x的正正方形中包含着包括它自身共x个正方形。

    于是我们推出式子:

    $sumlimits_{i=1}^{min(n,m)}{i*(n-i+1)*(m-i+1)}$

    (i枚举边长,$(n-i+1)和(m-i+1)$的意义为平移后的个数。详情参见本人题解:数三角形)

    然后就可以在O(n)时间内求出sum0。

    处理sum1:

    这道题最精髓(毒瘤)的部分。(查了若干份题解才搞明白还有一大堆结论没证……

    依然通过观察和理性思考,我们发现这样一个性质:

    如果一个坏点在某一个正正方形的边上或者端点上,

    那么它唯一对应这个正正方形中存在的某一个顶点均在正正方形边上的正方形(斜正方形或正正方形本身)

    于是我们可以只去考虑该坏点位于正正方形的边上或者点上的情况。

    问题转化为:过该坏点的正正方形有多少个。

    枚举每一个坏点,我们考虑分情况讨论:该坏点在某个正方形的四个点上或四个边上。

    四个顶点好求。由于保证是正正方形,我们只需要保证它不超过边界。

    设该正方形距离左边界的距离为l,距右边界距离为r,距上边界距离为u,距下边界距离为d。

    于是十分愉快一行取min走起:$sum1+=min(l,u),sum+=min(l,d),sum+=min(r,u),sum+=min(r,d)$;

    接下来考虑坏点位于四个边上。

    颓了一些性质(自己至今没有证上来……心里发虚)

    我们只考虑左边界、右边界和上边界的关系来统计,其他直接旋转推广。

    设该坏点距左边界的距离为x,距右边界的距离为y,距上边界的距离为h,

    则有:

    如果h<=min(x,y),对答案的贡献为h*(h+1)/2%mod;

    如果min(x,y)<h<=max(x,y),对答案的贡献为min(x,y)*(min(x,y)+2)+min(x,y)*(h-min(x,y))

    如果max(x,y)<h<=x+y,对答案的贡献为

    min(x,y)*(min(x,y)+1)/2+min(x,y)*(max(x,y)-min(x,y))+(min(x,y)-1+min(x,y)-h+max(x,y))*(h-max(x,y))/2

    如果h>x+y,对答案的贡献为x*y。

    推广到四个方向,分别将左右上边界附成(l,r,u)(l,r,d)(u,d,l)(u,d,r)分别计算贡献即可。

    处理sum2:

    处理$sum2$的时候需要注意,我们处理出来的sum2是至少包含2个坏点的正方形总数。所以无需保证正方形的另外两个点不是坏点(我因为没想透卡了一小会儿)

    所以我们只需要枚举两个坏点作为正方形的两个顶点,然后讨论这两个坏点作为正方形的边上的顶点和对角线上的顶点两种情况,讨论另外两个点是否在界内以及是否整点来统计答案。

    作为边:1.另外两个点是$(A+D-B,B+A-C)(C+D-B,D+A-C)$

        2.另一方向的$(A+B-D,B+C-A)(C+B-D,D+C-A)$。

    作为对角线,(利用全等三角形解方程可得):另外两个点是$((A-D+B+C)/2,B+C-(A-D+B+C)/2)((A-B+D+C)/2,A+D-(A-B+D+C)/2)$。

    处理sum3、sum4:

    这个显然和sum2放到一起就可以了。

    网上的题解基本上一笔带过。然而细节还是需要处理一下的。

    首先是判定问题。我最开始打了个暴搜判定,枚举每一个坏点坐标,匹配上了就返回1,否则一直搜到末尾返回0。

    T到了2000ms。(真的飞起来了欸!)

    然后学题解打hash链表,超快的说。783ms解决问题。

    然而我sb的把模数设到了1e8+7,觉得可以借这个题给模数省一个define……

    然后他就给了我一堆美妙的re。最开始re30,把数组压到1e7是re40,我意识到事情不太对,赶紧重开模数给它削了俩0,A了……

    (好长啊 /仰望,给个推荐呗~)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<cmath>
    #define mod 100000007
    #define ll long long
    #define int long long
    #define K 1000007
    using namespace std;
    int n,m,k;
    int sum0,sum1,sum2,sum3,sum4;
    struct node{int x,y;}wor[300003];
    struct hsss{int key,nxt;}data[300003];
    int num,h[10000003];
    //bool iswor[3003][3003];
    inline int query(int x,int y)
    {
        for(register int i=1;i<=k;++i)
            if(wor[i].x==x&&wor[i].y==y)
                return 1;
        return 0;
    }
    inline void ins(int key)
    {
        int x=key%K;
        for(int i=h[x];i;i=data[i].nxt)
            if(data[i].key==key)return;
        data[++num].key=key;data[num].nxt=h[x];h[x]=num;
    }
    inline int cal(int x,int y,int h)
    { 
        h-=1;
        if(x>y)swap(x,y); 
        if(h<=x)return (ll)h*(h+1)/2%mod;
        if(h<=y)return ((ll)x*(x+1)/2+(ll)x*(h-x))%mod; 
        if(h>=x+y)return (ll)x*y%mod; 
        return ((ll)x*(x+1)/2+(ll)x*(y-x)+(ll)(x-1+x-h+y)*(h-y)/2)%mod;
    }
    /*inline bool query(int key)
    {
        int x=key%K;
        for(int i=h[x];i;i=data[i].nxt)
            if(data[i].key==key) return 1;
        return 0;
    }*/
    inline void check(int x1,int y1,int x2,int y2)
    { 
        if(x1<0||x1>n||x2<0||x2>n||y1<0||y1>m||y2<0||y2>m)return; 
        int res=query(x1,y1)+query(x2,y2);
        sum2+=1;if(sum2>=mod)sum2-=mod;
        sum3+=res;if(sum3>=mod)sum3-=mod;
        sum4+=(res==2);if(sum4>=mod)sum4-=mod;
    }
    signed main()
    {
        scanf("%lld %lld %lld",&n,&m,&k);
        for(register int i=1;i<=k;++i){scanf("%lld %lld",&wor[i].x,&wor[i].y);ins(wor[i].x*(m+1)+wor[i].y);}
        for(register int i=1;i<=min(n,m);++i){sum0+=i*(n-i+1)*(m-i+1)%mod;sum0%=mod;}
        for(register int i=1;i<=k;++i)
        {
            int l=wor[i].x,r=n-wor[i].x,u=wor[i].y,d=m-wor[i].y;
            sum1+=min(l,u)+min(l,d)+min(r,u)+min(r,d);
            if(sum1>=mod)sum1-=mod;
            sum1+=cal(l,r,u);if(sum1>=mod)sum1-=mod;
            sum1+=cal(l,r,d);if(sum1>=mod)sum1-=mod;
            sum1+=cal(u,d,l);if(sum1>=mod)sum1-=mod;
            sum1+=cal(u,d,r);if(sum1>=mod)sum1-=mod;
    /*        
            int l=wor[i].x,r=m-wor[i].x,u=wor[i].y,d=n-wor[i].y;
            int mn=min(l,r),mx=max(l,r);
            if(u<=mn)sum1+=(u+3)*u/2;
            else if(u>mn&&u<=mx)sum1+=(mn+3)*mn/2+(u-mn)*(mn+1);
            else if(u>=mx+mn+1)sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn+1)*mn/2;
            else sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn*2+1-u+mx)*(u-mx)/2;
            if(d<=mn)sum1+=(d+3)*d/2;
            else if(d>mn&&d<=mx)sum1+=(mn+3)*mn/2+(d-mn)*(mn+1);
            else if(d>=mx+mn+1)sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn+1)*mn/2;
            else sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn*2+1-d+mx)*(d-mx)/2;
            mn=min(u,d),mx=max(u,d);
            if(l<=mn)sum1+=(l+3)*l/2;
            else if(l>mn&&l<=mx)sum1+=(mn+3)*mn/2+(l-mn)*(mn+1);
            else if(l>=mx+mn+1)sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn+1)*mn/2;
            else sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn*2+1-l+mx)*(l-mx)/2;
            if(r<=mn)sum1+=(r+3)*r/2;
            else if(r>mn&&r<=mx)sum1+=(mn+3)*mn/2+(r-mn)*(mn+1);
            else if(r>=mx+mn+1)sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn+1)*mn/2;
            else sum1+=(mn+3)*mn/2+(mx-mn)*(mn+1)+(mn*2+1-r+mx)*(r-mx)/2;
    */
        }
        for(register int i=1;i<k;++i)
        {
            for(register int j=i+1;j<=k;++j)
            {
                int x1=wor[i].x,y1=wor[i].y,x2=wor[j].x,y2=wor[j].y;
                check(x1+y2-y1,y1+x1-x2,x2+y2-y1,y2+x1-x2);
                check(x1+y1-y2,y1+x2-x1,x2+y1-y2,y2+x2-x1);
                if((x1+x2+y1+y2)&1) continue;
                check(x1+x2+y2-y1>>1,y1+y2+x1-x2>>1,x1+x2+y1-y2>>1,y1+y2+x2-x1>>1);
    /*
                if(A+D-B>0||B+A-C>0||A+D-B<m||B+A-C<n)
                if(C+D-B>0||C+D-B<m||D+A-C>0||D+A-C<n)
                {
                    sum2++;
                    if(query(A+D-B,B+A-C)||query(C+D-B,D+A-C))sum3++;
                    if(query(A+D-B,B+A-C)&&query(C+D-B,D+A-C))sum4++;
                }
                if((A-D+B+C)%2)continue;if((A-D+B+C)%2)continue;
                if((A-B+D+C)%2)continue;if((A-B+D+C)%2)continue;
                if((A-D+B+C)/2>0&&(A-D+B+C)/2<m&&B+C-(A-D+B+C)/2>0&&B+C-(A-D+B+C)/2<n)
                if((A-B+D+C)/2>0&&(A-B+D+C)/2<m&&A+D-(A-B+D+C)/2>0&&A+D-(A-B+D+C)/2<n)
                {
                    sum2++;
                    if(query((A-D+B+C)/2,B+C-(A-D+B+C)/2)||query((A-B+D+C)/2,A+D-(A-B+D+C)/2))sum3++;
                    if(query((A-D+B+C)/2,B+C-(A-D+B+C)/2)&&query((A-B+D+C)/2,A+D-(A-B+D+C)/2))sum4++;
                }
    */
            }
        }
    //    cout<<sum0<<" "<<sum1<<" "<<sum2<<" "<<sum3<<" "<<sum4<<endl;
        cout<<((sum0-sum1+sum2-sum3/3+sum4/6)%mod+mod)%mod<<endl;
        return 0;
    }
    View Code

    ps:注释掉的是我的调试和重构代码前的代码……写崩了……

  • 相关阅读:
    c++类的知识点(1)
    并查集经典例题分析
    并查集
    bfs-迷宫
    出栈次序--数学归纳法--蓝桥
    九宫重排
    Tomcat详解
    寒假日记-第三天
    寒假日记-第二天(MySQL语句)
    Java学期总结
  • 原文地址:https://www.cnblogs.com/xingmi-weiyouni/p/11237709.html
Copyright © 2020-2023  润新知