1. 程式人生 > >[BZOJ4558/LOJ2025/Luogu3271][GZOI2016/JLOI2016/SHOI2016]方

[BZOJ4558/LOJ2025/Luogu3271][GZOI2016/JLOI2016/SHOI2016]方

http 方向 進行 src log abcd struct ++i 方法

題目鏈接:

4558:[JLoi2016]方-BZOJ

#2025.「JLOI/SHOI2016」方-LOJ

P3271 [JLOI2016]方-Luogu

一個簡單的容斥題(然後自己卡住一直沒出來

首先不考慮“壞點”,那麽如何計算正方形個數呢?

換一種思路,枚舉正好包住正方形的正方形大小(有點拗口),再統計裏面有多少個正好在此正方形上的正方形。

如圖,此時枚舉的正方形為\(EFGH\),邊長為\(i\),統計有多少正方形頂點“貼”在此正方形上。

技術分享圖片

那麽對於一個點\(A\),可以和一條邊上的\(i\)個點對應,那麽就有\(i\)種正方形。

邊長為\(i\)\(EFGH\)有多少個?當然是\((n-i+1)*(m-i+1)\)

個了~。

於是初始答案即為\(\sum_{i=1}^{\min(n,m)}i*(n-i+1)*(m-i+1)\)

對此可以\(O(n)\)地計算(其實還有\(O(log)\)的方法)

接著對於壞點,首先統計有多少正方形至少在一個“壞點”上。

那麽對於一個點\(A(x,y)\),只需分別統計其對於正上方,正下方,左方和右方的貢獻,最後減去重疊的部分即可。

若點\(A(x,y)\)在正方形\(ABCD\)上,那麽就會有一個正方形\(EFGH\)正好包含\(ABCD\)

若現在統計對於正上方的貢獻,那麽\(A\)就在\(EFGH\)的底邊上。

技術分享圖片

如圖,\(P1\sim P4\)為整個網格圖。

\(EFGH\)

的邊長為\(i\),則\(i\)的取值範圍就是\([1,t=\min(AB,AC+AD)]\),此時\(EFGH\)\(i+1\)種選法(\(A\)與底邊\(i+1\)個點一一對應)。

\(t\le AC\)\(t\le AD\),那麽方案數就是\(\sum_{i=1}^t (i+1)=\frac{t(t+3)}2\)

\(t>AC\),那麽就要把超出去的給剪掉。

\(t-AC=a\),那麽就要減去\(\frac{a(a+1)}2\)

\(t>AD\)同理。

對於其他三個方向同理。

最後要減去四個方向的重疊部分(也就是一個點是\(A\)\(EFGH\)

那麽對於正上方和左方,重復的個數就是\(\min(AB,AC)\)

\(EFGH\)\(A\)為右下角,向左上延伸)。

其他的重疊同理。

接著你會發現統計完\(1\)個"壞點"之後,這樣會重復統計\(2\)個壞點的正方形,那麽繼續容斥,加上\(2\)個的,再減\(3\)個的,最後加上\(4\)個點全是壞點的正方形個數。

至於如何統計這些,可以枚舉正方形的兩個點,分\(3\)種情況求出正方形,如下圖。

技術分享圖片

求出另外兩個點判斷是否存在即可。

註意這樣會重復統計,最後\(3\)個的要除以\(C_3^2\)\(4\)個的除以\(C_4^2\)(有多少種方案統計此正方形)。

如何判斷存在?你可以用\(set\)或者二分,我這裏用了\(Hash\)表,樂觀情況下是\(O(1)\)的。

於是上面的步驟時間復雜度為\(O(k^2)/O(k^2log_2k)\)

那麽這題就愉快地做完了。

代碼:

#include <cstdio>
typedef long long ll;

inline int Abs(const int x){return x>=0?x:-x;}
inline int Min(const int a,const int b){return a<b?a:b;}
inline int Max(const int a,const int b){return a>b?a:b;}

int n,m,k,xs[2005],ys[2005];
const int Mod=100000007;
struct Hash_Table//哈希表
{
    int Head[1000005],Next[2005],Vx[2005],Vy[2005],En;

    inline void Insert(const int x,const int y)
    {
        int Hv=x*1LL*y%1000003;
        Next[++En]=Head[Hv];
        Head[Hv]=En;
        Vx[En]=x,Vy[En]=y;
    }

    bool Find(const int x,const int y)
    {
        int Hv=x*1LL*y%1000003;
        for(int i=Head[Hv];i;i=Next[i])
            if(Vx[i]==x&&Vy[i]==y)
                return true;
        return false;
    }
}Map;

int Get(int l,int r,int h)
//求一個壞點一個方向的貢獻
//AB=h,AC=l,AD=r
{
    int t=Min(h,l+r);
    int Res=t*(t+3LL)/2%Mod;
    if(t>l)Res=(Res-(t-l)*(t-l+1LL)/2)%Mod;
    if(t>r)Res=(Res-(t-r)*(t-r+1LL)/2)%Mod;
    return (Res+Mod)%Mod;
}

int C2,C3,C4;
//壞點數為2,3,4的正方形數量

inline void Check(int ax,int ay,int bx,int by)
//正方形另外兩點為(ax,ay),(bx,by),進行統計
{
    if(ax<0||ax>n||ay<0||ay>m)return;
    if(bx<0||bx>n||by<0||by>m)return;
    //不合法情況
    int Cnt=0;
    if(Map.Find(ax,ay))++Cnt;
    if(Map.Find(bx,by))++Cnt;
    ++C2;
    if(Cnt>=1)++C3;
    if(Cnt>=2)++C3,++C4;
    //分別累積
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    int Ans=0;
    for(int i=1;i<=Min(n,m);++i)
        Ans=(Ans+(n-i+1LL)*(m-i+1)%Mod*i)%Mod;//初始的方案數
    for(int i=1;i<=k;++i)
    {
        scanf("%d%d",&xs[i],&ys[i]);
        Map.Insert(xs[i],ys[i]);
        int a=xs[i],b=ys[i],c=n-a,d=m-b;
        int Cnt=((ll)Get(a,c,b)+Get(a,c,d)+Get(b,d,a)+Get(b,d,c))%Mod;
        //四個方向的貢獻
        Cnt=(Cnt-Min(a,b)-Min(b,c)-Min(c,d)-Min(a,d))%Mod;
        //容斥掉重復部分
        Ans=(Ans-Cnt)%Mod;
    }
    for(int i=1;i<k;++i)
        for(int j=i+1;j<=k;++j)
        {
            int dx=xs[i]-xs[j],dy=ys[i]-ys[j];
            Check(xs[i]+dy,ys[i]-dx,xs[j]+dy,ys[j]-dx);
            Check(xs[i]-dy,ys[i]+dx,xs[j]-dy,ys[j]+dx);
            //(i,j)兩點為正方形一邊。
            if((Abs(dx)+Abs(dy))&1)continue;
            //不能當對角線
            int nx=(dx-dy)>>1,ny=(dx+dy)>>1;
            Check(xs[i]-nx,ys[i]-ny,xs[j]+nx,ys[j]+ny);
            //這裏的式子很簡單就不說了
        }
    printf("%lld\n",(((ll)Ans+C2-C3/3+C4/6)%Mod+Mod)%Mod);
    //Final容斥
    return 0;
}

[BZOJ4558/LOJ2025/Luogu3271][GZOI2016/JLOI2016/SHOI2016]方