【树套树】【XSY1952】【BZOJ3295】动态逆序对

\(Description\)

对于序列\(A\),它的逆序对数定义为满足\(i<j\),且\(A_{i}\)>\(A_{j}\)的数对\((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


\(HINT\)

样例解释
\((1,5,3,4,2)⇒(1,3,4,2)⇒(3,4,2)⇒(3,2)⇒(3)。\)

\(M≤N≤100000\)


思路

我们第一眼看上去,哇,三维偏序

再看一眼,哇,待修三维偏序

于是我们选择用树套树实现

在这里我们选择树状数组套权值线段树

但是我们发现,如果直接开点可能会爆空间?!

那就动态开点线段树咯

我们在树状数组每个节点开一棵线段树,维护每个区间权值不同的数的个数

而树状数组用前缀和维护权值大于的关系

最后每删去一个数就减去这个数对逆序对的贡献


这里特殊把\(getans\)拎出来讲讲

long long getans(int x,int p)
{
    return query(p,n)-query(p,x)+query(n,x)-query(p,x);
}

这是在删除一个数时维护对逆序对的贡献的代码

\(query(x,y)\)是查询下标小于\(x\)的数中值小于\(y\)的数的个数

于是,上面这段代码我们可以分为两段理解

\(1.query(p,n)-query(p,x)\)表示在小于\(p\)的下标中,值在\(x\)\(n\)中的数的个数
\(2.query(n,x)-query(p,x)\)表示在\(p\)\(n\)的下标中,值小于\(x\)的数的个数

显而易见,这两个加起来就是一个数对逆序对的贡献,减去即可


完整代码:

#include<bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
const int N=100010;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int n,m;
int pos[N];
int c[N];
int rt[N];
int cnt=0;
struct tree
{
    int sum,ch[2];
}t[N*100];
void add(int x,int y)
{
    for(;x<=n;x+=lowbit(x))c[x]+=y;
}
void modify(int &k,int l,int r,int x,int val)
{
    if(!k)k=++cnt;
    t[k].sum+=val;
    //动态开点
    if(l==r)return ;
    int mid=(l+r)>>1;
    if(x<=mid)modify(t[k].ch[0],l,mid,x,val);
    else modify(t[k].ch[1],mid+1,r,x,val);
}
void ins(int x,int p,int v)
{
    for(;x<=n;x+=lowbit(x))modify(rt[x],1,n,p,v);
}
long long getnum(int x)
{
    long long ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
long long ask(int k,int l,int r,int p)//查询在区间内小于p的数
{
    if(l==r)return t[k].sum;
    int mid=(l+r)>>1;
    if(p<=mid)return ask(t[k].ch[0],l,mid,p);
    return t[t[k].ch[0]].sum+ask(t[k].ch[1],mid+1,r,p);
}
long long query(int x,int p)
{
    long long ans=0;
    for(;x;x-=lowbit(x))ans+=ask(rt[x],1,n,p);
    return ans;
}
long long getans(int x,int p)
{
    return query(p,n)-query(p,x)+query(n,x)-query(p,x);
}
int main()
{
    n=read(),m=read();
    int x;
    long long ans=0ll;
    for(int i=1;i<=n;i++)
    {
        x=read();
        pos[x]=i;
        ans+=getnum(n)-getnum(x);//在没有修改之前,直接用树状数组前缀和查询大于x的数个数,满足逆序对
        add(x,1);//将x的树状数组+1
        ins(i,x,1);//加入线段树中
    }
    for(int i=1;i<=m;i++)
    {
        printf("%lld\n",ans);
        x=read();
        ans-=getans(x,pos[x]);//减去这个点对逆序对的贡献
        ins(pos[x],x,-1);//在线段树中-1
    }
    return 0;
}

转载于:https://www.cnblogs.com/ShuraEye/p/11396863.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值