[BZOJ1176]-Mokia-CDQ分治

本文分享了一道关于矩形差分的算法题解过程,包括遇到的问题、解决思路及优化方法。通过拆分询问利用前缀和进行计算,采用CDQ分治策略配合树状数组实现高效查询。

说在前面

有点时间没有写这种矩形差分的题了,忘记了较小的边界要-1…WA了几次
然后就开始各种优化,然而还是进不了第一页=A=

另外,第一次单日提交满50次纪念!
这里写图片描述
(纪念个MMP啊= =,劳资还想交题呢)


题目

BZOJ1176传送门

题面

维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.

输入输出格式

输入格式:
第一行两个整数,S,W;其中S为矩阵初始值;W为矩阵大小
接下来每行为一下三种输入之一(不包含引号):
“1 x y a”
“2 x1 y1 x2 y2”
“3”
输入1:你需要把(x,y)(第x行第y列)的格子权值增加a
输入2:你需要求出以左下角为(x1,y1),右上角为(x2,y2)的矩阵内所有格子的权值和,并输出
输入3:表示输入结束

输出格式:
对于每一个询问,输出一个数字表示答案,答案保证在int范围内


解法

推荐这篇,写得比较详细
传送门 for BraketBN’s blog
单独看每个询问,我们可以把每个对于矩形的询问,拆成四个对于前缀和的询问(前缀和差分思想)
可以发现,对拆分之后的询问有贡献的修改操作,一定是时间在它之前的,并且x,y坐标都要小于等于它的,很明显是一个三维偏序,然后CDQ就好了= =
具体来说:x纬度用sort,时间纬度在二分保证,y纬度使用树状数组查询前缀和


下面是自带大常数的代码

实现的时候,me把y坐标离散化了,这样可以使树状数组那个log变小很多
不离散化也可以过,大概是7秒左右

/**************************************************************
    Problem: 1176
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:2712 ms
    Memory:32232 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int S , W , topp , cnta , uninum ;
int ans[10005] ;
struct Options{
    int x , y , delta , tim , id , opt ;
    Options(){} ;
    Options( int x_ , int y_ , int delta_ , int tim_ , int id_ , int opt_ ) :
        x(x_) , y(y_) , delta(delta_) , tim(tim_) , id(id_) , opt(opt_){} ;
    bool operator < ( const Options &A ) const {
        if( x == A.x && y == A.y ) return opt < A.opt ;
        if( x == A.x ) return y < A.y ;
        return x < A.x ;
    }
}o[640005] , tmpL[320005] , tmpR[320005] ;
struct BIT{
    int b[350000] ;
    void add( int x , int delta ){
        for( ; x <= uninum ; x += x&-x )
            b[x] += delta ;
    }
    int Query( int x ){
        int rt = 0 ;
        for( ; x ; x -= x&-x )
            rt += b[x] ;
        return rt ;
    }
}B;

inline bool cmpy( const Options &A , const Options &B ) {
    return A.y < B.y ;
}

void CDQ( int lf , int rg ){
    if( lf == rg ) return ;
    int Rcnt = 0 , Lcnt = 0 , mid = ( lf + rg ) >> 1 ;
    for( int i = lf ; i <= rg ; i ++ ){
        if( o[i].tim <= mid && o[i].opt == 1 ) B.add( o[i].y , o[i].delta ) ;
        if( o[i].tim >  mid && o[i].opt == 2 ) ans[ o[i].id ] += B.Query( o[i].y ) * o[i].delta ;
    }
    for( int i = lf ; i <= rg ; i ++ ){
        if( o[i].tim <= mid && o[i].opt == 1 ) B.add( o[i].y , -o[i].delta ) ;
        if( o[i].tim <= mid ) tmpL[++Lcnt] = o[i] ;
        if( o[i].tim >  mid ) tmpR[++Rcnt] = o[i] ;
    }
    for( int i = 1 ; i <= Lcnt ; i ++ ) o[lf+i-1] = tmpL[i] ;
    for( int i = 1 ; i <= Rcnt ; i ++ ) o[lf+Lcnt-1+i] = tmpR[i] ;
    CDQ( lf , mid ) ; CDQ( mid+1 , rg ) ;
}

void solve(){
    sort( o + 1 , o + topp + 1 , cmpy ) ;
    for( register int i = 1 , last = -1 ; i <= topp ;i ++ ){
        if( last != o[i].y ) uninum ++ , last = o[i].y ;
        o[i].y = uninum ;
    }
    sort( o + 1 , o + topp + 1 ) ;
    for( register int i = 1 ; i <= W ; i ++ )
        B.add( i , S ) ;
    CDQ( 1 , topp ) ;
    for( int i = 1 ; i <= cnta ; i ++ )
        printf( "%d\n" , ans[i] ) ;
}

inline int read_(){
    char ch = getchar() ;
    int rt = 0 , flag = 1 ;
    while( ch < '0' || ch > '9' ){
        if( ch == '-' ) flag = -1 ;
        ch = getchar() ;
    }
    while( ch >='0' && ch <='9' ) rt = rt * 10 + ch - 48 , ch = getchar() ;
    return rt * flag ;
}

int main(){
    scanf( "%d%d" , &S , &W ) ;
    for( register int a , b , c , d , delta , opt ; ; ){
        scanf( "%d" , &opt ) ;
        if( opt == 1 ){
            a = read_() , b = read_() , delta = read_() ;
            o[++topp] = (Options){ a , b , delta , topp , 0 , 1 } ;
        } else if( opt == 2 ){
            ++ cnta ;
            a = read_() , b = read_() , c = read_() , d = read_() ;
            o[++topp] = (Options){ a-1 , b-1 , 1 , topp , cnta , 2 } ;
            o[++topp] = (Options){ a-1 , d , -1 , topp , cnta , 2 } ;
            o[++topp] = (Options){ c , b-1 , -1 , topp , cnta , 2 } ;
            o[++topp] = (Options){ c , d , 1 , topp , cnta , 2 } ;
        } else break ;
    }
    solve() ;
}
/*
0 4
1 2 3 3
2 1 1 3 3
3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值