[BZOJ3295]动态逆序对CDQ分治

来源:互联网 发布:泰国直播软件 编辑:程序博客网 时间:2024/06/05 14:09

这,其实如果真的想清楚了,大概···其实就一个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 ;}
阅读全文
0 0
原创粉丝点击