线段树 --- 单点更新、求逆序对、离散化
来源:互联网 发布:巴南银针 淘宝 编辑:程序博客网 时间:2024/06/06 04:30
G.Inversions
There are N integers (1<=N<=65537)A1, A2,.. AN (0<=Ai<=10^9). You need to find amount of such pairs (i, j)that 1<=i<j<=N and A[i]>A[j].
Input
The first line of the input contains thenumber N. The second line contains N numbers A1...AN.
Output
Write amount of such pairs.
Sample Input
Sample test(s)
Input
5
2 3 1 5 4
Output
3
【题目来源】:BUN
【题目大意】:求逆序对的个数
【题目分析】
求逆序对有很多方法,比如说用合并排序、分治、树状数组、线段树,甚至连暴力(冒泡排序)也可以做,但是要注意会不会超时。
这里就讲一下树状数组的方法,这一题最有意思的是离散化的方法,这个方法在处理大数据的排序方面很有用,离散化能够有效的降低时空复杂度,他可以改进一个低效的算法。除了加上了一个离散化,其他的用树状数组就可以解决。
1、什么是离散化?
用我的理解来说就是一种映射,为什么能用离散化呢?或者说离散化能用在哪些方面呢?
举个例子说吧 ,在排序、求逆序对这些和顺序有关的题目中就能用离散化。搜索帖子你会发现有各种说法,比如“排序后处理”、“对坐标的近似处理”等等。哪个是对的呢?哪个都对。关键在于,这需要一些例子和不少的讲解才能完全解释清楚。
下面就模拟一个数列的离散化:
首先定义一个结构体:
struct Node
{
long long num;
int index;
};
Node node[MAX];
和一个数组a[MAX],然后输入五个数:8 1 6 7 4
for(i=1;i<=n;++i)
{
scanf("%lld",&node[i].num);
node[i].index=i;
}
node[i].num存储了数组的值,而node[i].index存储的是他的下标,也就是序号。
按照num来进行排序:
bool cmp(Node a,Node b)
{
return a.num<b.num;
}
stable_sort(node+1,node+n+1,cmp);//这是一个重点,后面会单独讲一下。
排好序以后再用:
for(i=1;i<=n;i++)
{
a[node[i].index]=i;
}
这样就将原来的8 1 6 7 4转化为5 1 3 4 2,比较一下这个序列和原来的序列有什么区别?你会发现他记录了原来数组的大小和元素顺序,这两个就通过离散化很好的结合在一起了,而且将原来很大的数据压缩为一串从1开始的连续的数,大大降低了时空复杂度。
然后问题就简单了,将a数组更新到树状数组当中去,进行统计就出答案了。
void update(int x)
{
while(x<MAX)
{
tree[x]++;
x+=lowbit(x);
}
}
开始的时候我一直都没弄清楚树状数组是怎么实现求逆序对的,后来也是看别人的博客,模拟了一下才搞懂的。
原理是什么呢?
在插入a[i]之前,我们先统计比a[i]小的数或等于a[i]的数有几个,也就是getsum(a[i]),然后再用i-getsum(a[i]),这样就得到了在他前面并且比他大的数据的个数。这样也很好理解,总数-小于或等于本身的个数=大于本身的个数,先更新再统计。
还有这题需要用long long ,开始的时候没想到害我wrong了好多次,然后静下心来算了一算发现确实要用long long,在从下往上累加的时候,最上面的那个数最大时相当于65537的平方,65537的平方就是四十多亿,int最多二十亿,妥妥的超了。用long long就过了。还有一个细节,就是当输入的序列中有数字相同时要怎么处理?首先处理这个问题时你得透彻的知道离散化的过程。
有两种解决方法:
第一种:在离散化的过程中进行处理,也就是在离散化过程中遇到两个数相同时,你得将它标记为两个数,这样就避免了相同时只计算一个数这种情况。
第二种:直接用stable_sort,即:
stable_sort(node+1,node+n+1,cmp);
现在就来讲一下sort和stable_sort的区别:
这两个函数的原理都是快速排序,时间复杂度在所有排序中最低,为O(nlog2n) ;但是stable_sort要稍微慢一点。
sort的应用;
1、可以传入两个参数;
sort(a,a+N) ,其中a是数组,a+N表示对a[0]至a[N-1]的N个数进行排序(默认从小到大排序);
2、传入三个参数;
sort(a,a+N,cmp),第三个参数是一个函数 ;
如果让函数从大到小排序,可以用如下算法实现;
bool cmp(int a,intb){return a>b};
sort(A,A+N,cmp);
而stable_sort的用法与sort一致,区别是stable_sort函数遇到两个数相等时,不对其交换顺序;这个应用在数组里面不受影响,当函数参数传入的是结构体时,会发现两者之间的明显区别。
这题如果在离散化的时候不进行处理,后面又用sort,肯定妥妥的跪了,我就是这样啊,血的教训。。。
最好的解决办法就是不管什么情况下都用stable_sort,这样就不会出现这种问题了。
下面贴一下代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define MAX 70000
using namespace std;
long long tree[MAX];
long long a[MAX];
struct Node
{
long long num;
int index;
};
Node node[MAX];
bool cmp(Node a,Node b)
{
return a.num<b.num;
}
int lowbit(int x)
{
return x&(-x);
}
void update(int x)
{
while(x<MAX)
{
tree[x]++;
x+=lowbit(x);
}
}
int getsum(int x)
{
int sum=0;
while(x>0)
{
sum+=tree[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)//这题不能用while(scanf("%d",&n),n),为此wrong了n次
{
memset(tree,0,sizeof(tree));
int i,j,k,l;
for(i=1;i<=n;++i)
{
scanf("%lld",&node[i].num);
node[i].index=i;
}
stable_sort(node+1,node+n+1,cmp);
// check
// for(i=1;i<=n;i++)
// {
// printf("%d ",node[i].num);
// }
//对排序后的数组进行离散化
for(i=1;i<=n;i++)
{
a[node[i].index]=i;
}
// for(i=1;i<=n;i++)
// {
// printf("%d ",a[i]);
// }
//入树+统计
long long ans=0;
for(i=1;i<=n;i++)
{
update(a[i]);
// cout<<getsum(a[i])<<endl;
ans+=(i-getsum(a[i]));
}
printf("%lld\n",ans);
}
return 0;
}
以上是树状数组的做法,现在我们来讨论一下线段树的做法:
很简单…… 设数列为a,将数列离散化,在从前往后枚举,统计答案…… 离散化:例如2 5 8 3 10 等价于 1 3 4 2 5,可以通过排序加小小处理解决。 枚举到第i个数,我们需要求出从1到i-1中有多少个比a[i]大的数,更新答案。 具体怎么做呢? 每次枚举完一个数之后,将这个数插入到线段树里,插入到线段树的神马地方呢?当然是这个数多大就插入到多大的地方。 举个例子:3 2 4 1。则线段树的变化应该为:tree[3]+=1;tree[2]+=1;tree[4]+=1;tree[1]+=1; 设x=a[i],这样,在插入一个数X时,首先求一下tree[x+1]~tree[n]的和,这个和就是1~i-1中有多少个比a[i]大的数。运用线段树求和可以做到O(n log n),具体实现请看下面。
首先我们需要分析一下线段树在这个问题中起到的作用,通过以上的思路分析,我们知道线段树在这一题中的作用是维护个数,换句话说就是记录该数字的出现的个数,每当我们更新一个数的时候,在线段树中找到该点,然后直接+1就可。
//Memory Time // K MS #include<algorithm> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<vector> #include<queue> #include<stack> #include<iomanip> #include<string> #include<climits> #include<cmath> #define MAX 65537 #define LL long long using namespace std; struct Node { int num; int index; }; Node a[MAX]; int aa[MAX]; int n; int ans; struct Tree{ int l,r,num; }; Tree tree[MAX*3]; bool cmp(Node a,Node b){ return a.num<b.num; }//总结:在建树的过程中,不要使用树的左右儿子来作为判断条件,因为此时树还未建完 void Build_tree(int l,int r ,int x){ //(1,n,1) tree[x].l=l; tree[x].r=r; tree[x].num=0; int tmp=2*x; if(l==r) return ; int mid=(l+r)>>1; Build_tree(l,mid,2*x); Build_tree(mid+1,r,2*x+1); } void update(int x,int k,int num){ //1 插入的位置 插入的值 if(tree[x].l==tree[x].r){ tree[x].num+=num; return ; } int tmp=2*x; int mid=(tree[x].l+tree[x].r)>>1; if(k<=mid) update(tmp,k,num); else update(tmp+1,k,num); tree[x].num=tree[tmp].num+tree[tmp+1].num; } void getans(int l,int r,int x){ //左端点 右端点 1 if(tree[x].l==l&&tree[x].r==r){ ans+=tree[x].num; return ; } int tmp=2*x; int mid=(tree[x].l+tree[x].r)>>1; if(r<=mid) getans(l,r,tmp); else if(l>mid) getans(l,r,tmp+1); else{ getans(l,mid,tmp); getans(mid+1,r,tmp+1); } } int main() { // freopen("cin.txt","r",stdin); // freopen("cout.txt","w",stdout); while(~scanf("%d",&n)){ for(int i=1;i<=n;i++){ scanf("%d",&a[i].num); a[i].index=i; } stable_sort(a+1,a+1+n,cmp); aa[0]=0; for(int i=1;i<=n;i++){ aa[a[i].index]=i; } LL sum=0; memset(tree,0,sizeof(tree)); Build_tree(1,n,1); // update(1,4,10); // ans=0; // getans(1,3,1); // cout<<ans<<endl; //correct for(int i=1;i<=n;i++){ ans=0; getans(1,n,1); int x=ans; ans=0; getans(1,aa[i],1); int y=ans; // cout<<"a="<<a<<" "<<"b="<<b<<endl; sum+=(x-y); update(1,aa[i],1); } cout<<sum<<endl; } return 0; } //求逆序对的基本思路: //先离散化,然后在将数a更新进线段树之前将线段树进行统计 //线段树的作用: //记录插入数字的个数,快速算出在某个区间中有多少个已经插入的数字
- 线段树 --- 单点更新、求逆序对、离散化
- POJ.2299 Ultra-QuickSort (线段树 单点更新 区间求和 逆序对 离散化)
- 线段树求逆序数(单点更新)
- 【线段树+离散化】求逆序对数目
- inv 线段树,逆序对,离散化
- HDU 1394 线段树单点更新求逆序数
- HDU 1394(单点更新线段树求逆序数)
- HDU1394 线段树 单点更新 求逆序数
- 线段树单点更新+区间更新+离散化
- HDU 5273 Dylans loves sequence(线段树求逆序数对+离散化)
- 线段树求逆序数(离散化)POJ 2299
- poj2299Ultra-QuickSort【线段树求逆序数】离散化
- NYOJ 117 求逆序数(离散化+线段树)
- openjudge 求逆序对数(线段树+离散化)
- poj 2299 Ultra-QuickSort 线段树求逆序数+离散化||归并排序求逆序数
- 树状数组+离散化求逆序对
- hdu 1394 Minimum Inversion Number(线段树之 单点更新求逆序数)
- HDU 1394 Minimum Inversion Number (线段树 单点更新 求逆序数)
- 基于DataChannel.js(WebRTC_Experiment)实现的多端点的通信
- 菜鸟学习的开始
- rsync 的核心算法
- <<Oracle Applications DBA 基础(第二期)>>Week 11 exercise
- (转)C++开发BHO(以HelloWorld对话框为例子)
- 线段树 --- 单点更新、求逆序对、离散化
- 使用PHP导入和导出CSV文件
- [LeetCode] Validate Binary Search Tree
- Uva 1388 Graveyard 解题报告(数学)
- 第一周工作报告
- codeblocks单点调试教程
- 6174问题
- Golang后台开发初体验
- A strange lift