【cqoi2011】动态逆序对

时间限制:1秒  内存限制:64M

【问题描述】

  对于序列A[i],它的逆序对数定义为满足:i < j,且A[i] > A[j]的数对(i,j)的个数。

  给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

【输入格式】

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

【输出格式】

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

【输入样例】

5 4
1
5
3
4
2
5
1
4
2

【输出样例】

5
2
2
1

【数据范围】

编号  1-2   3-4    5-6   7-8    9-10
n   <=1000  <=30000 <=50000 <=60000 <=100000
m   <=100   <=10000 <=20000 <=40000 <=50000

【来源】

(1,5,3,4,2) -> (1,3,4,2) -> (3,4,2) -> (3,2) -> (3)。

今天晚上一直在做这道题,做了几种方法出来,但时间复杂度各不相同,准确的说只是常数不同,但就是这个常数有2种方法看起来可以却要被卡住1~2个点。

第一种方法:树套树,常数极高,可能是因为我用的线段树套平衡树,线段树的常数大了点。

第二种:分块+排序,普通的分块方法,就按照要求,分成几块,然后块内排序。删除就找前面比他大的和后面比他小的就好。会卡常数一个点。

第三种:分块+bit,也是分块,而且这种方法也要优化才能过,用bit记录块内的值,bit[i][j]前i个块一共有多少个在j所属的范围内。这个前缀和的思想可以省很多时间。

第四种:CDQ分治,也要优化,优化了特别快,没有优化特别慢,最可怕的是不容易看出来优化在哪里。

第五种:持续化线段树:思想和分块加bit的优化版差不多。

这里我提供一下CDQ分治的代码(因为难想),一个优化了的,一个没优化的,其他代码就不给了,请读者自己好好写,都不难但是请主要细节。

#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1000005;

struct shu
{
    int x,id,y;
    ll ans;
    friend bool operator <(shu a,shu b)
    {
        return a.yint n,m,bit[maxn],b[maxn];

ll lowbit(ll x){
    return x&(-x);
}

void in(ll x){
    for(;x<=n;x+=lowbit(x))
    bit[x]++;
}

void out(ll x){
    for(;x<=n;x+=lowbit(x))
    bit[x]=0;
}

ll find(ll x){
    ll t=0;
    for(;x>=1;x-=lowbit(x))
    t+=bit[x];
    return t;
}

bool cmp(shu a,shu b){
    return a.id/*这是没优化的
void work(int l,int r){
    if(l==r) return;
    int m=(l+r)>>1;
    work(l,m);
    work(m+1,r);
    sort(a+l,a+m+1);
    sort(a+m+1,a+r+1);
    int j=l;
    for(int i=m+1;i<=r;i++)
    {
        while(j<=m&&a[j].y=l)
    {
        out(n+1-a[j].x);
        j--;
    }
    j=m;
    for(int i=r;i>m;i--)
    {
        while(j>=l&&a[j].y>a[i].y)
        {
            in(a[j].x);
            j--;
        }
        a[i].ans+=find(a[i].x-1);
    }
    while(j<=m)
    {
        out(a[j].x);
        j++;
    }
}
*/
//这是优化了的,请自行对比。(可以复制下来试试速度,保证长见识)
void work(int l,int r){
    if(l==r) return;
    int m=(l+r)>>1;
    work(l,m);
    work(m+1,r);
    sort(a+l,a+r+1);
    int j=l;
    for(int i=l;i<=r;i++)
    {
        if(a[i].id<=m) in(n+1-a[i].x);
        else a[i].ans+=find(n-a[i].x);
    }
    for(int i=l;i<=r;i++)
    if(a[i].id<=m) out(n+1-a[i].x);
    j=m;
    for(int i=r;i>=l;i--)
    {
        if(a[i].id<=m) in(a[i].x);
        else a[i].ans+=find(a[i].x-1);
    }
    for(int i=l;i<=r;i++)
    if(a[i].id<=m) out(a[i].x);
}
int read(){
    int x=0;
    bool ok=0;
    char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    while((ch>='0'&&ch<='9')||ch=='-')
    {
        if(ch=='-') ok=1;
        else x=x*10+ch-'0';
        ch=getchar();
    }
    return ok?-x:x;
}
char ch[50];
void out2(ll x){
    int len=0;
    if(!x) putchar('0');
    while(x) 
    {
      ch[len++]=x%10+'0';
      x/=10;
    }
    for(int i=len-1;i>=0;i--) putchar(ch[i]);
    putchar('\n');
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        a[i].x=read();
        a[i].ans=0;
        a[i].y=i;
        a[i].id=0;
        b[a[i].x]=i;
    }
    int x,t=0;
    for(int i=1;i<=m;i++)
    {
        x=read();
        a[b[x]].id=n+1-i;
    }
    for(int i=1;i<=n;i++) if(a[i].id==0) a[i].id=++t;
    sort(a+1,a+1+n,cmp);
    work(1,n);
    sort(a+1,a+1+n,cmp);
    ll ans=0;
    for(int i=1;i<=n;i++)
    ans+=a[i].ans;
    for(int i=n;i>=n+1-m;i--)
    {
        out2(ans);
        ans-=a[i].ans;
    }
    return 0;   
}

你可能感兴趣的:(线段树,平衡树,可并堆,CDQ分治,可持久化)