[BZOJ3295]动态逆序对CDQ分治

这,其实如果真的想清楚了,大概···其实就一个CDQ模板题
只是这道题在每个CDQ执行的时候,影响需要分两部分计算


题解

题目我就不写了······直接写方法
我们可以把删除操作倒着看成插入,这样对于每一个数字就多了一个时间维,在时间约束下的逆序对,就是三位偏序。
第一维是插入时间,第二维是位置,第三维是值。

判断依据:

1.插入时间早的对插入时间晚的可能产生贡献
2.位置靠前并且值比较大的,对位置靠后并且值比较小的产生贡献
3.位置靠后并且值比较小的,对位置靠前并且值比较大的产生贡献

具体操作

先按照时间从小到大sort一次,然后在每个小分制里,把区间分成左右两部分,左边的时间一定比右边的时间小。

用两个数组L,R分别存起来,然后按照位置从小到大sort一次。two pointer枚举每个R[i],将位置比R[i]小的存入树状数组。存完每个R[i]之后,立即在树状数组里查询当前比R[i]值大的数的个数,加到这个数的答案里。
同样的操作再来一遍,只不过这一次的判断依据是按照位置靠后值较小来判断。

注意最后输出答案的时候,并不是直接输出答案。
因为时间靠后的答案里一定包括时间靠前的答案,需要把答案累计上来再输出,具体实现方法很多


代码

丑死了···写过的最丑的CDQ,别人几十行就能搞完,然而我的这···要百多行
哭唧唧

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

int N , M , varc[100005] , ans[100005] , addv[50005] ;
long long Ans ;
struct BIT{
    int b1[100005] ;
    void clear(){
        memset( b1 , 0 , sizeof( b1 )) ;
    }
    void add_in( int x , int delta ){
        for( ; x <= N ; x += (x&-x) )   b1[x] += delta;
    }
    int Query( int x ){
        int rt = 0 ;
        for( ; x ; x -= (x&-x) )    rt += b1[x] ;
        return rt ;
    }
}b;
struct Data{
    int t , v , p ;
}d[100005] , l[50005] ,r[50005] ;

bool cmp_t( const Data &a , const Data &b ){
    return a.t < b.t ;
}

bool cmp_p( const Data &a , const Data &b ){
    if( a.p == b.p ) return a.v < b.v ;
    return a.p < b.p ;
}

void read__(){
    int delv , cnt  ;
    scanf( "%d%d" , &N , &M ) ; cnt = N ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%d" , &d[i].v ) ;
        d[i].p = i ; varc[ d[i].v ] = i ;
    }
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d" , &delv ) ;
        d[ varc[delv] ].t = cnt-- ;
    }
    for( int i = 1 ; i <= N ; i ++ )
        if( !d[i].t ) d[i].t = cnt-- ;
}

void CDQ( int lf , int rg ){
    if( lf == rg ) return ;
    int mid = ( lf + rg ) >> 1 ;
    CDQ( lf , mid ) ;
    int Lsiz = mid - lf + 1 , Rsiz = rg - mid ;
    memcpy( l+1 , d+lf , Lsiz*sizeof( Data )) ;
    sort( l+1 , l+Lsiz+1 , cmp_p ) ;//第二维偏序,保证左右区间的p分别有序
    memcpy( r+1 , d+mid+1 , Rsiz*sizeof( Data )) ;
    sort( r+1 , r+Rsiz+1 , cmp_p ) ;

    int tp1 = 1 , tp2 = 1 , tot = 0 ;
    for( ; tp2 <= Rsiz ; tp2++ ){
        for( ; l[tp1].p < r[tp2].p && tp1 <= Lsiz ; tp1++ ){
            b.add_in( l[tp1].v , 1 ) ;//对t比自己小,p也比自己小的,将其v
            addv[++tot] = l[tp1].v ;  //插入树状数组查询
        }
        ans[ r[tp2].t ] += b.Query( N ) - b.Query( r[tp2].v ) ;
        //查询v也比自己小的,累加到答案里
        //注意到,这里并不是一次性把答案算完,而是每次分治都累加一些当前
        //分制区间对该点有贡献的点
        //这里是正着的,当前是较小的,大的在前面
    }
    for( int i = 1 ; i <= tot ; i ++ )
        b.add_in( addv[i] , -1 ) ;

    //这里是判断倒着的逆序对,当前是较大的,小的在后面
    tp1 = Lsiz , tp2 = Rsiz , tot = 0 ;
    for( ; tp2 >= 1 ; tp2-- ){
        for( ; l[tp1].p > r[tp2].p && tp1 >= 1 ; tp1-- ){
            b.add_in( l[tp1].v , 1 ) ;
            addv[++tot] = l[tp1].v ; 
        }
        ans[ r[tp2].t ] += b.Query( r[tp2].v ) ;
    }
    for( int i = 1 ; i <= tot ; i ++ )
        b.add_in( addv[i] , -1 ) ;
    CDQ( mid+1 , rg ) ;
}

int main(){
    read__() ;
    sort( d+1 , d+N+1 , cmp_t ) ;//第一维偏序,保证t有序
    CDQ( 1 , N ) ;
    for( int i = 1 ; i <= N ; i ++ )
        Ans += ans[i] ;
    for( int i = N ; i > N-M ; i -- )
        printf( "%lld\n" , Ans ) , Ans -= ans[i] ;
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值