HDU 5273 Dylans loves sequence——BestCoder Round #45(DP or 树状数组)

Dylans loves sequence

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)


Problem Description
Dylans is given  N  numbers  a[1]....a[N]

And there are  Q  questions.

Each question is like this  (L,R)

his goal is to find the “inversions” from number  L  to number  R .

more formally,his needs to find the numbers of pair( x,y ),
that  Lx,yR  and  x<y  and  a[x]>a[y]
 

Input
In the first line there is two numbers  N  and  Q .

Then in the second line there are  N  numbers: a[1]..a[N]

In the next  Q  lines,there are two numbers  L,R  in each line.

N1000,Q100000,LR,1a[i]2311
 

Output
For each query,print the numbers of "inversions”
 

Sample Input
   
   
   
   
3 2 3 2 1 1 2 1 3
 

Sample Output
   
   
   
   
1 3
Hint
You shouldn't print any space in each end of the line in the hack data.
 

Source
BestCoder Round #45
 
/************************************************************************/

附上该题对应的中文题

Dylans loves sequence

 
 
 Time Limit: 2000/1000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
Dylans得到了NN个数a[1]...a[N]a[1]...a[N]。
有QQ个问题,每个问题形如(L,R)(L,R)
他需要求出L-RLR这些数中的逆序对个数。
更加正式地,他需要求出二元组(x,y)(x,y)的个数,使得L \leq x,y \leq RLx,yRx < yx<ya[x] > a[y]a[x]>a[y]
输入描述
第一行有两个数NNQQ。
第二行给出NN个数字a[1]...a[N]a[1]...a[N]。
接下来的QQ行,每行给出两个数L, RL,RN \leq 1000,Q \leq 100000,L \leq R,1 \leq a[i] \leq 2^{31}-1N1000,Q100000,LR,1a[i]2311
输出描述
对于每个询问,输出逆序对个数。
输入样例
3 2
3 2 1
1 2
1 3
输出样例
1 
3
Hint
hack数据里读入的每一行末尾不应该有多余的空格。
/****************************************************/

出题人的解题思路:

N只有10001000,于是想怎么来就怎么来。

最容易想到的是枚举开头,然后Nlog(N)Nlog(N)时间里去算逆序对,用一个树状数组维护。 (可惜BC不给卡。。。呜呜呜)

仔细一想发现可以很简单地做到N^{2}N2.

ans[l][r]ans[l][r]l \sim rlr的逆序对数量。首先我们暴力地先算好ans[1][1..N]ans[1][1..N]。 然后ii2 \sim N2N枚举,每次计算从ii开始的逆序对。

那么ans[i][j]ans[i][j]ans[i-1][j]ans[i1][j]少了什么呢?没错,少了a[i-1]a[i1]这个数的贡献。 我们再开一个累加器cntcnt。枚举jji \sim NiN,如果a[i-1]a[i1]a[j]a[j]产生逆序对就cnt[j]=-1cnt[j]=1 然后我们从右往左累加cntcnt(因为贡献是前缀和性质的)

最后ans[i][j]=ans[i-1][j]+cnt[j]ans[i][j]=ans[i1][j]+cnt[j]。 预处理完所有的答案就可以O(1)O(1)的询问啦。

本题要我们求的就是区间[l,r]内逆序对的个数,首先,我们假设s[i][j]为i~j的逆序对的数量,这样我们可以初步算出s[i][i+1...N]中与i能形成逆序对的数量。然而,如果我们单纯只算这一步的话,会少算了点东西
举个例子
比如说我们通过[1,2]的逆序对数求出了[1,3]的逆序对数,当然这并不是完整的1~3范围的逆序对,它缺了2~3内的逆序对数
所以我们还得反向将结果整合一下,即先正向计算出[1,2]、[1,3]、[2,3],再反向将[2,3]整合到[1,3]中。方向的计算公式是s[j][i]+=s[j+1][i]

貌似涉及到这方面的题目,讲起来总是有点混乱,不过大家可以多多交流,这样能够理解得更透彻

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<stdlib.h>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 1005;
const int inf = 1000000000;
int s[N][N],a[N];
int main()
{
    int n,q,i,j,k,l,r;
    while(~scanf("%d%d",&n,&q))
    {
        for(i=1;i<=n;i++)
            scanf("%d",&a[i]);
        memset(s,0,sizeof(s));
        for(i=1;i<=n;i++)
            for(j=i+1;j<=n;j++)
            {
                s[i][j]+=s[i][j-1];
                if(a[j]<a[i])
                    s[i][j]++;
            }
        for(i=n;i>0;i--)
            for(j=i-1;j>0;j--)
                s[j][i]+=s[j+1][i];
        for(i=0;i<q;i++)
        {
            scanf("%d%d",&l,&r);
            printf("%d\n",s[l][r]);
        }
    }
    return 0;
}

另一种方法就是用树状数组来进行预处理(虽然此题用树状数组稍显麻烦)

因为区间[l,r]之间的逆序对个数s[l][r]=区间[l,r-1]之间的逆序对个数+区间[l,r-1]加入第r个数a[r]所增加的逆序对个数

区间[l,r-1]之间的逆序对个数是已知的,关键就在于怎么用树状数组来求区间[l,r-1]加入第r个数a[r]所增加的逆序对个数

现在可以这么思考,我要想知道加入a[r]之后,增加了多少的逆序对,只要知道区间[l,r-1]内有多少比a[r]来得大的数即可,而这一步就可以用到树状数组。

在计算a[r]时,前面的a[l]到a[r-1]都已经对树状数组进行更新

比如说3 2 1,计算[1,2]时,[1,1]已经求出,且3已经对树状数组进行更新,当你加入第2个数2时,树状数组中比2大的数有1个,即3,树状数组总的和-小于等于2的数的个数就是大于2的数的个数,那就是加入2之后增加的逆序对个数

可能举例之后你还是不太明白我到底在说什么,但是不要紧,有问题可以提,我会尽快予以解答

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<stdlib.h>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 1005;
const int inf = 1000000000;
int a[N],b[N],c[N],s[N][N];
int lowbit(int t) //计算c[t]展开的项数
{
    return t&(-t);
}
int Sum(int n) //求前n项的和.
{
    int sum=0;
    while(n>0)
    {
        sum+=c[n];
        n-=lowbit(n);
    }
    return sum;
}
void update(int i)
{
    while(i<=b[0])
    {
        c[i]++;
        i+=lowbit(i);
    }
}
int main()
{
    int i,j,m,n,p,q,l,r;
    while(~scanf("%d%d",&n,&q))
    {
        for(i=1;i<=n;i++)
            scanf("%d",&a[i]),b[i]=a[i];
        sort(b+1,b+n+1);//将数组b排序
        b[0]=unique(b+1,b+n+1)-(b+1);//去除数组b中相邻的重复元素
        for(i=1;i<=n;i++)
            a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
            //姑且可以叫做数据压缩吧,因为只要考虑大小关系,所以1 5 8完全可以记为1 2 3,减小树状数组的整体大小
        for(i=1;i<=n;i++)
        {
            memset(c,0,sizeof(c));
            for(j=i;j<=n;j++)
            {
                s[i][j]=s[i][j-1]+Sum(b[0])-Sum(a[j]);
                update(a[j]);
            }
        }
        while(q--)
        {
            scanf("%d%d",&l,&r);
            printf("%d\n",s[l][r]);
        }
    }
}

菜鸟成长记


你可能感兴趣的:(动态规划,ACM,预处理,树状数组)