BZOJ 3295 [Cqoi2011] 动态逆序对 CDQ分治题解

Description

对于序列A,它的逆序对数定义为满足i

Input

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。

Output

输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1

样例解释

(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。
HINT

N<=100000 M<=50000


这道题是CDQ分治裸,于是我们CDQ分治愉快水之,第一维是这个数的位置,第二维是这个数的大小,第三维是时间序,其中时间序的定义为,我们把所有删除点的操作倒过来看,如果一个点第一个被删除的话,可以看成是最后一个被插入,这样一来,后面的点影响前面的点,而在计数的时候,永远都是前面的点去影响后面的点,于是我们可以得出时间序的给出方法,先把所有的需要删减的给出时间序,倒着放置,第一个被删除的就当做是第n个被插入的,把所有的删除操作进行完了之后,再把所有的其他数按照从左至右的顺序放入,时间序为1,2,3等等
cdq之前先按照时间排序
进cdq之后每次cdq时把所有的这个区间的数组分成两部分,一部分在前,一部分在后,然后讨论前面的对后面的影响,再考虑后面的对前面的影响,因为是按照t排序的,因此在这个块内一定满足左边的t是小于右边的t,因此考虑左边对右边的影响的时候,就是先把左边的东西插入,然后查询右边的(如果右边的操作时查询操作的话)已经有多少个点的值大于这个点但是坐标比这个点小,计算右边的点对左边的点的影响的情况反之


#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 100010;
int n, m, C[MAXN], arc[MAXN], ans[MAXN];
long long ANS;
struct data{ int x,y,t; int flag; }a[MAXN],b[MAXN];
bool cmp1( data te, data mp ) { if( te.x == mp.x ) return  te.y < mp.y; return te.x < mp.x; }
bool cmp( data te, data mp ) { return te.t < mp.t; }
void add( int x, int num ) {
    while( x <= n ){
        C[x] += num;
        x += x & (-x);
    }
}
int query( int x ) {
    int tmp = 0;
    while( x ) {
        tmp += C[x];
        x -= x & (-x);
    }
    return tmp;
}
void CDQ( int l, int r ) {
    if( l >= r ) return ;
    int mid = ( l + r ) >> 1;
    int cnt = 0;
    for( register int i = l; i <= mid; i++ ) b[++cnt] = a[i], b[cnt].flag = 0;
    for( register int i = mid + 1; i <= r; i++ ) b[++cnt] = a[i], b[cnt].flag = 1;
    sort( b + 1, b + cnt + 1, cmp1);
    for( register int i = 1; i <= cnt; i++ ) 
        if( b[i].flag == 0 ) add( b[i].y, 1 );
        else                 ans[b[i].t] += query(n) - query(b[i].y);
    for( register int i = 1; i <= cnt; i++ ) 
        if( b[i].flag == 0 ) add( b[i].y, -1 );
    for( register int i = cnt; i >= 1; i-- ) {
        if( b[i].flag == 0 ) add( b[i].y, 1 );
        else                 ans[b[i].t] += query( b[i].y );
    } 
    for( register int i = 1; i <= cnt; i++ ) 
        if( b[i].flag == 0 ) add( b[i].y, -1 );
    CDQ( l, mid );
    CDQ( mid + 1, r );
}
void solve( ) {
    scanf( "%d%d", &n, &m );
    for( register int i = 1; i <= n; i++ ) {
        a[i].x = i;
        scanf( "%d", &a[i].y );
        arc[a[i].y] = i;
    } int tmp = n, x; 
    for( register int i = 1; i <= m; i++ ) {
        scanf( "%d", &x );
        a[arc[x]].t = tmp--; 
    }
    for( register int i = 1; i <= n; i++ ) 
        if( a[i].t == 0 ) a[i].t = tmp--;
    sort( a + 1, a + n + 1, cmp ); CDQ( 1, n );
    for( register int i = 1; i <= n; i++ ) ANS += ans[i];
    for( register int i = n; i > n - m; i-- ) {
        printf( "%lld\n", ANS );
        ANS -= ans[i];
    }
}
int main( ) {
    solve( );
    return 0;
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值