1. 程式人生 > >牛客練習賽29 C題 枇杷

牛客練習賽29 C題 枇杷

題意:

二維平面的第一象限內,有兩種操作,一是在某個點的權值加一,二是查詢一個直角梯形範圍內的權值的和。

題解:

std解法是CDQ分治,想了個分塊暴力的方法,速度竟然是最快的。。。

將整個第一象限分為128*128(1<<7)個塊,每個塊是邊長為(1<<23)的正方形,這樣可以表示座標(1<<30)範圍內的點,滿足題目給出的範圍

建立一個128*128的二維樹狀陣列,用來求一個矩形區域內的權值的和

對於第一種操作,求出點所在的塊,在二維樹狀陣列中單點修改就行

關鍵是第二種操作

一個直角梯形:

分解為:

矩形和直角三角形分開計算

矩形部分:

先確定矩形的邊界佔據的塊,即求出矩形所在塊的x方向和y方向的範圍:l , r , dn , up

那麼,範圍 l+1 ~ r-1 , dn+1 ~ up -1 內的塊中的點一定是在矩形範圍內的,就是下圖中的綠色部分

綠色部分的權值和用二維樹狀陣列解決,4條邊界所經過的塊就必須要遍歷一遍,對於每一個塊,看塊中包含的點在矩形內的個數,兩部分加一起就是矩形範圍內的權值和

然後是三角形部分

由於不是矩形,無法通過二維樹狀陣列一次求解,方法是遍歷左右區間,即 x 方向在 [ l+1 , r-1 ] ,y方向為 dn+1 的塊,就是綠色塊的底邊,通過直線方程可以計算出當前列的綠色塊的上限,顯然每一列的個數是不同的,這樣一列一列計算出綠色塊包含的權值和(這裡由於是一列,所以可以用一維樹狀陣列求解),再遍歷邊界經過的塊,兩部分加一起就是三角形範圍內的權值和

具體實現起來方法很多,不同方法需要考慮的細節不一樣,下面的方法需要考慮的細節比較少。

複雜度分析

最壞1e5個點,1e4多個塊,由於資料隨機,平均一個塊不到10個點

操作一複雜度為二維樹狀陣列複雜度:log*log,即7*7

操作二複雜度

矩形:一個二維樹狀陣列查詢:log*log,邊界經過的塊的期望值為128個,每個塊平均有10個點,總複雜度為128*10

三角形:邊界經過的塊的期望值為128個,求綠色塊時每一列查詢一次一維樹狀陣列,單次為log,共128*7,邊界複雜度同矩形,為128*10,總複雜度為128*10

所以,操作二複雜度為128*10

由於操作一二是隨機出現,實際複雜度遠遠達不到上限,原因:首先前期每個塊中點的個數遠達不到10,後期個數可能會多一點,但這一定是操作一過多導致的,這又導致操作二變得很少,於是發現真正耗時間的操作二少很多。一次操作姑且期望為 1e3,共有1e5個操作,總用時1e8

 

程式碼:

#include<bits/stdc++.h>
#define N 100010
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 3.141592653589793
#define mod 998244353
#define LL long long
#define pb push_back
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
struct Point
{
    int x,y;
};
vector<Point> a[1<<7][1<<7];
int dd[1<<7][1<<7],f[1<<7][1<<7],op,x,y,xx,d;

inline void update(int x,int y)
{
    for (int i=x;i<128;i|=(i+1))
        for (int j=y;j<128;j|=(j+1))
            dd[i][j]++;
    for (int i=y;i<128;i|=(i+1))
        f[x][i]++;
}

inline int sum(int x,int y)
{
    int res=0;
    for (int i=x;i>=0;i=(i&(i+1))-1)
        for (int j=y;j>=0;j=(j&(j+1))-1)
            res+=dd[i][j];
    return res;
}

inline int gsum(int x,int y)
{
    int res=0;
    for (int i=y;i>=0;i=(i&(i+1))-1)
        res+=f[x][i];
    return res;
}

inline int spyer(int l,int r,int dn,int up)  //矩形
{
    if (r>127) r=127;
    if (up>127) up=127;
    int res=0,t=xx+d+y;
    for (int i=l;i<=r;i++)
    {
        for (int j=0;j<a[i][up].size();j++)  //上邊界
        {
            int tx=a[i][up][j].x,ty=a[i][up][j].y;
            if (tx>=x && tx<=xx && ty<=y+d && ty>=y) res++;else
            if (tx>xx && tx<=xx+d && ty>=y && tx+ty<=t) res++;
        }
        if (dn<up)
        for (int j=0;j<a[i][dn].size();j++)  //下邊界
        {
            int tx=a[i][dn][j].x,ty=a[i][dn][j].y;
            if (tx>=x && tx<=xx && ty>=y && ty<=y+d) res++;else
            if (tx>xx && tx<=xx+d && ty>=y && tx+ty<=t) res++;
        }
    }

    for (int i=dn+1;i<up;i++)
    {
        for (int j=0;j<a[l][i].size();j++)  //左邊界
        {
            int tx=a[l][i][j].x,ty=a[l][i][j].y;
            if (tx>=x && tx<=xx && ty>=y && ty<=y+d) res++;else
            if (tx>xx && tx<=xx+d && ty>=y && tx+ty<=t) res++;
        }
        if (l<r)
        for (int j=0;j<a[r][i].size();j++) //右邊界
        {
            int tx=a[r][i][j].x,ty=a[r][i][j].y;
            if (tx>=x && tx<=xx && ty>=y && ty<=y+d) res++;else
            if (tx>xx && tx<=xx+d && ty>=y && tx+ty<=t) res++;
        }
    }

    l++;r--;dn++;up--;
    if (l>r || dn>up) return res;else
    return res+sum(r,up)-sum(l-1,up)-sum(r,dn-1)+sum(l-1,dn-1);
}

inline int spyerr()  // 三角形
{
    int res=0;
    int nx=((xx>>23)+1)<<23,t=xx+d+y;  //三角形的左邊界在矩形上其實已經算過了,重新確定需要計算的左邊界,一定是某一個塊的起始位置
    xx+=d;
    if (nx>xx) return 0;
    if ((nx>>23)>127) return 0;
    int l=nx>>23,r=xx>>23,dn=y>>23;
    for (int i=l;i<=r;i++)  // 計算下邊界
    {
        for (int j=0;j<a[i][dn].size();j++)
        {
            int tx=a[i][dn][j].x,ty=a[i][dn][j].y;
            if (tx>=nx && tx<=xx && ty>=y && tx+ty<=t) res++;
        }
    }
    int ny=(dn+1)<<23; y=t-nx; //重新確定需要計算的下邊界,一定是某一個塊的起始位置
    xx=t-ny; r=xx>>23;
    if (ny>y) return res;
    for (int i=l;i<=r;i++)
    {
        int tx=i<<23,ttx=((i+1)<<23)-1,ty=t-tx,tty=t-ttx,t1=ty>>23,t2=tty>>23;  //當前列的塊的座標左右邊界所處的塊為t1,t2
        if (t1<128) for (int j=0;j<a[i][t1].size();j++)  // 斜邊經過的塊的計算
        {
            int tx=a[i][t1][j].x,ty=a[i][t1][j].y;
            if (tx>=nx && tx<=xx && ty>=ny && tx+ty<=t) res++;
        }
        if (t2!=t1 && t2>dn && t2<128)
        for (int j=0;j<a[i][t2].size();j++)   // 斜邊經過的塊的計算
        {
            int tx=a[i][t2][j].x,ty=a[i][t2][j].y;
            if (tx>=nx && tx<=xx && ty>=ny && tx+ty<=t) res++;
        }
        if (t2>127) t2=128;
        if (t2-1>dn)res+=gsum(i,t2-1)-gsum(i,dn);   //綠色的塊計算
    }
    return res;
}

int main()
{
    int n;
    sc(n);
    for (int i=1;i<=n;i++)
    {
        sc(op);
        if (op==1)
        {
            scc(x,y);
            a[x>>23][y>>23].pb(Point{x,y});
            update(x>>23,y>>23);
        }else
        {
            scc(x,y); scc(xx,d);
            int tmp=spyer(x>>23,xx>>23,y>>23,(y+d)>>23);
            tmp+=spyerr();
            printf("%d\n",tmp);
        }
    }
}