洛谷 P3157 [CQOI2011]动态逆序对 分块

题目描述

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

输入输出格式

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

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

输入输出样例

输入样例#1:
5 4
1
5
3
4
2
5
1
4
2
输出样例#1:
5
2
2
1

样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。
说明

N<=100000 M<=50000

分析:
本来打的树套树,没打完就感冒了= =。仔细一想还不如打分块。我们每删掉一个数,相当于减去前面比他大的个数+后面比他小的个数。于是,我们可以把每个块先排个序,二分这个块。对于自己所在块,暴力该块减小的逆序对,并删掉这个数(暴力把比他大的前移)。不懂为什么%lld会错,最后打了cout。至于一开始的逆序对数可以用树状数组暴力(真的超级暴力)。

代码:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>

const int maxn=1e5+7;

using namespace std;

int id[maxn],a[maxn],b[maxn],l[maxn],r[maxn],belong[maxn];
int n,m,block,sum,i,x,j;
long long ans=0;

long long t[maxn*5];

int lowbit(int x)
{
    return x&(-x);
}
void updata(int x,int k)
{
    while (x<=n)
    {
        t[x]+=(long long)k;
        x+=lowbit(x);
    }
}

long long getsum(int x)
{
    long long ans=0;
    while (x>0)
    {
        ans+=t[x];
        x-=lowbit(x);
    }
    return ans;
}

void kp(int l,int r)
{
    if (l>r) return;
    int i=l; int j=r;
    int temp;
    int key=b[(l+r)/2];
    while (i<=j)
    {
        while (b[i]<key) i++;
        while (b[j]>key) j--;
        if (i<=j)
        {
            temp=b[i]; b[i]=b[j]; b[j]=temp;
            i++; j--;
        }
    }
    kp(l,j);
    kp(i,r);
}

void build_block()
{
    block=trunc(sqrt(n*2));
    sum=n/block+(n%block!=0);
    for (int i=1;i<=sum;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[sum]=n;
    for (int i=1;i<=n;i++) belong[i]=(i-1)/block+1; 
    for (int i=1;i<=sum;i++) kp(l[i],r[i]); 
}

int find(int u,int l,int r)
{
    int ans=l-1,mid;
    while (l<=r)
    {
        mid=(l+r)/2;
        if (b[mid]<u) ans=mid,l=mid+1;
                 else r=mid-1; 
    }
    return ans;
}

int main()
{
    freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        id[a[i]]=i;
        b[i]=a[i];

    }   
    build_block();      
    for (i=1;i<=n;i++)
    {
        ans+=(i-1)-getsum(a[i]-1);
        updata(a[i],1);
    }
    cout<<ans;      
    printf("\n");
    for (j=1;j<m;j++)
    {       
        scanf("%d",&x);
        int u=id[x];
        int s=l[belong[u]+1]-1;
        if (s==-1) s=n;
        for (i=l[belong[u]];i<=s;i++)
        {
            if (a[i]==-1) continue;
            if ((a[i]>a[u]) && (i<u)) ans--;
            if ((a[i]<a[u]) && (i>u)) ans--;
        }
        a[u]=-1;        
        for (i=1;i<belong[u];i++)
        {
            ans-=r[i]-find(x,l[i],r[i]);
        }
        for (i=belong[u]+1;i<=sum;i++)
        {
            ans-=find(x,l[i],r[i])-l[i]+1;
        }
        int c=find(x,l[belong[u]],r[belong[u]]);
        for (i=c+1;i<r[belong[u]];i++) b[i]=b[i+1];
        r[belong[u]]--;
        cout<<ans;
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值