码迷,mamicode.com
首页 > 其他好文 > 详细

【CQOI2011】【BZOJ3295】动态逆序对

时间:2015-04-09 12:01:24      阅读:123      评论:0      收藏:0      [点我收藏+]

标签:cdq分治

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

N100000 M50000

Source

cdq分治过这题好简单啊www
据说主席树会被卡常数cdq完全不担心
用树状数组预处理出初始序列中逆序对个数,包括每一位的数对应的逆序对个数.
然后开一个数组叫new_inv,用来记录已经被删除的数构成的序列的逆序对个数(因为如果直接在ans里减掉每个数的逆序对,有一些逆序对会被重复计算进去)
然后就开始裸cdq分操作就可以了.
顺便其实这是我第一次用树状数组求逆序对.刚学的.仰慕PoPoQQQ神犇>ω<从他那里get了简单的树状数组函数的写法(因为我才刚会用树状数组两三天→_←别问我为什么才刚学会树状数组因为我是沙茶233)
关于树状数组求逆序对:
其实就相当于把那些数逐个插入进树状数组,看看插入进去的时候比它小的数有多少.
具体的可以看这篇文章

//AC code by CreationAugust
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 101000
#define lbt(x) (x&-x)//扯淡的算符优先级= =不加()就会bomb 
using namespace std;
int n,m;
long long ans;
int tot;
int a[MAXN],b[MAXN];//用于树状数组统计逆序对 
int new_inv[MAXN];//表示删除某个元素后,已经删除的元素构成的序列里的逆序对数目 
int tim[MAXN];
struct Query
{
    int num,pos;//num为删除的数,pos为num出现的位置,x为操作出现时间 
    int x;
    bool operator <(const Query& a)const{
        return pos<a.pos;
    }
}ques[MAXN],newq[MAXN];
int c[MAXN];
int num[MAXN];
void modify(int i,int flag)//这种写法把向下修改和向上修改写到一起 
{
    for (;i>0&&i<=n;i+=lbt(i)*flag) 
    {
        if (tim[i]!=tot)
            c[i]=0,tim[i]=tot;
        c[i]++;
    }
}
int sum(int i,int flag)//同上,两种查询压一起 
{
    int ret=0;
    for (;i>0&&i<=n;i+=lbt(i)*flag)
        if (tim[i]==tot) ret+=c[i];
    return ret;
}
void solve(int l,int r)
{
    int mid=(l+r)>>1,tp1=l,tp2=mid+1;
    if (l==r)
    {
        printf("%lld\n",ans);
        ans-=num[ques[l].pos];
        ans+=new_inv[l];//因为如果单纯删除的话,某个逆序对会被重复删除两次
        //因此要再加上删除它之后操作序列里新出现的逆序对 
        return;
    }
    for (int i=l;i<=r;i++)//按时间划分操作 
        if (ques[i].x<=mid) newq[tp1++]=ques[i];
        else newq[tp2++]=ques[i];
    memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
    solve(l,mid);
    tot++;
    int j=l;
    for (int i=mid+1;i<=r;i++)//更新新出现的逆序对 
    {
        for (;j<=mid&&ques[j].pos<ques[i].pos;j++)
            modify(ques[j].num,-1);
        new_inv[ques[i].x]+=sum(ques[i].num,1);
    }
    tot++;
    j=mid;
    for (int i=r;i>=mid+1;i--)
    {
        for (;j>=l&&ques[j].pos>ques[i].pos;j--)
            modify(ques[j].num,1);
        new_inv[ques[i].x]+=sum(ques[i].num,-1);
    }
    solve(mid+1,r);//剩余部分操作仍然按照之前的关键字排序的顺序分治 
    tp1=l,tp2=mid+1;
    for (int i=l;i<=r;i++)
        if ((ques[tp1]<ques[tp2]||tp2>r)&&tp1<=mid) newq[i]=ques[tp1++];
        else newq[i]=ques[tp2++];
    memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
}
int main()
{
    freopen("inverse.in","r",stdin);
    freopen("inverse.out","w",stdout);
    scanf("%d%d",&n,&m);
    //树状数组求出初始逆序对 
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
    for (int i=1;i<=n;i++)
    {
        num[i]=sum(a[i],1);
        modify(a[i],-1);
        ans+=num[i];
    }
    ++tot;
    for (int i=n;i>=1;i--)
    {
        num[i]+=sum(a[i],-1);
        modify(a[i],1);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&ques[i].num);
        ques[i].pos=b[ques[i].num];
        ques[i].x=i;
    }
    sort(ques+1,ques+m+1);//整个操作序列按pos关键字排序 
    solve(1,m);
}

【CQOI2011】【BZOJ3295】动态逆序对

标签:cdq分治

原文地址:http://blog.csdn.net/creationaugust/article/details/44956701

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!