您的位置:首页 > 产品设计 > UI/UE

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

2015-08-10 17:00 435 查看


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 L≤x,y≤R 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.

N≤1000,Q≤100000,L≤R,1≤a[i]≤231−1

 

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
a[1]...a[N]。
有QQ个问题,每个问题形如(L,R)(L,R)
他需要求出L-RL−R这些数中的逆序对个数。
更加正式地,他需要求出二元组(x,y)(x,y)的个数,使得L \leq x,y \leq RL≤x,y≤R且x < yx<y且a[x] > a[y]a[x]>a[y]


输入描述
第一行有两个数NN和QQ。
第二行给出NN个数字a[1]...a
a[1]...a[N]。
接下来的QQ行,每行给出两个数L, RL,R。

N \leq 1000,Q \leq 100000,L \leq R,1 \leq a[i] \leq 2^{31}-1N≤1000,Q≤100000,L≤R,1≤a[i]≤2​31​​−1


输出描述
对于每个询问,输出逆序对个数。


输入样例
3 2
3 2 1
1 2
1 3


输出样例
1
3


Hint
hack数据里读入的每一行末尾不应该有多余的空格。

/****************************************************/

出题人的解题思路:

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

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

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

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

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

最后ans[i][j]=ans[i-1][j]+cnt[j]ans[i][j]=ans[i−1][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

,a
;
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
,b
,c
,s

;
int lowbit(int t) //计算c[t]展开的项数
{
return t&(-t);
}
int Sum(int n) //求前n项的和.
{
int sum=0;
while(n>0)
{
sum+=c
;
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]);
}
}
}
菜鸟成长记
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: