既然不用做题了,就细细的理解一下知识点,决定学一点写一点,写点有用的东西。
首先是看了一下用树状数组求逆序数,看了好多版本,有直接求算的,有用结构体存取,再排序后计算的,但是总体的思路都是一样的,i-sum(i),用这个数减去之前输入的比他小的数的个数,总的看了一下,认为直接算更加方便而且好理解。
思路就是用树状数组存0或1,每输入一个,更新数组,并求算比他小的数的个数,ans+=i-sum(i)。最终输入完就得到了结果。
代码如下:
#include<iostream>#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
int c[100010];
int n;
int lowbit(int x){
return x&(-x);
}
void add(int x,int y){
for (i=x;i<=n;i+=lowbit(i)){
c[i]+=y;
}
}
int sum(int x){
int sum;
while (x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main(){
int i,j,k,l,ans,x;
while (cin>>n){
memset (c,0,sizeof(c));
ans=0;
for (i=1;i<=n;i++){
cin>>x;
add(x,1);
ans+=i-sum(x);
}
}
}
又看到了一个题目,是逆序数的一种应用,关键代码段还是:
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
S_L[i]=sum(a[i]);
add(a[i],1);
}
所以这也就是树状数组的一大应用,也就是求算某个位置的数左或是右比他大的(或者是比他小的)数的个数,特殊一点也就是逆序数。
这种题型的做法就是用树状数组存0 1,用sum计数比他小的数的个数得以求算。
然后就是这道题:
题意:
给你一个n个整数组成的序列,每次只能交换相邻的两个元素,问你最少要进行多少次交换才能使得整个整数序列上升有序。
这道题乍一看和树状数组毫无关系,借助数学知识:也就是我们上学期所学的高代的第二章置换当中讲到的,每进行一次对换,逆序数减少一,而当逆序数变为0的时候,这个序列就是上升有序的了。这样一做解释,这个题目确实不难,就是求这个序列的逆序数。
而这个看完这个题目的博客,我终于懂了为什么要用结构体存取的方法以及具体是怎么用了(这样写也是有好处的,自己慢慢学习,发现不足)。
也就是说,如果要求的数极其的大(比如到了10亿)这样的数的大小如果用正常的解法开个10亿的数组就会导致mle,这时候就用到了结构体处理数据,由于数的最大值虽然很大,但是数的数量却没有那么大,这时候,我们把数据进行集中处理,因为1和2的逆序数和1和100000的逆序数是相同的,从而达到处理数据压缩数组的效果。
关键代码如下:(有关于如何求逆序数的代码我就没在写,还是老的套路,只写了关键的处理数据的代码):
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
struct point{
int x;//x表示输入的这个数是多少
int y;//y表示这个数是第几个被输入进去的
bool operator <(const point&b) const{
return x<b.x;
}
};
struct point p[500010];
int a[500010];
int n;
int main(){
int i,j,k,l;
cin>>n;
for (i=1;i<=n;i++){
cin>>p[i].x;
p[i].y=i;
}
sort (p+1,p+n+1);
a[p[1].y]=1;
for (i=2;i<=n;i++){
if (p[i],x==p[i-1].x){
b[p[i].y]=b[p[i-1]].y;
}
else {
b[p[i].y]=i;
}
}
}
POJ 2352 Stars(树状数组)
http://poj.org/problem?id=2352
题意:
二维平面给定n个点(任意两个点不重合)的坐标,然后要你输出每个点的“等级“。每个点的等级是它的左下放的点个数(包括正下放和正左方的点)。即要你输出对于每个点(x,y)来说,有多少点的坐标(xi, yi)满足xi<=x且yi<=y。
而关于这道题,我对于博客上的解释略蒙,但根据他贴出来的代码,大概的意思就是这个题的输入格式是按照y从小到大来的,所以,“新来”的点一定在之前所有点的上方,那么,我们只需要对于x建立一个一维的树状数组,每输入一个数x,就求算sum(x)也就是之前输入的x比他小的数(也就是他的左下方的数,因为一定是在下面,只要x<新的x,那么这个点就在新的点的左下方),然后处理add(x,1),反复处理之后得到结果。之后看的几个题都是平淡无奇的有关于求逆序数的题,突然看到的一个题让我很是惊诧,早上的时候还在想,树状数组处理不了区间更新的问题,所以专门学习了一下线段树相关的更新区间的知识,那个dalao的算法让我看的云里雾里,这个博客的有关树状数组求区间和的解释便是拨云见雾。
先看看题目:
HDU 1556 Color the ball(树状数组)
http://acm.hdu.edu.cn/showproblem.php?pid=1556
#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
int n;
int a[1000010];
int lowbit(int x){
return x&(-x);
}
int usum(int x){
int sum=0;
while (x<=n){
sum+=a[x];
x=x+lowbit(x);
}
}
int dsum(int x){
int sum=0;
while (x>0){
sum+=a[x];
x=x-lowbit(x);
}
}
void uadd(int x,int y){
while (x<=n){
a[x]+=y;
x+=lowbit(x);
}
}
void dadd(int x,int y){
while (x>0){
a[x]+=y;
x-=lowbit(x);
}
}
int main(){
int l,r;
memset (a,0,sizeof(a));
while (cin>>l>>r){
uadd(l,1);
uadd(r+1,-1);
for (int i=l;i<=r;i++){
cout<<dsum(i)<<endl;
}
}//这段代码是常用的向上更新向下求和,sum(i)表示第i个数被染色了几次
memset (a,0,sizeof(a));
while (cin>>l>>r){
dadd(l-1,-1);
dadd(r,1);
for (int i=l;i<=r;i++){
cout<<usum(i)<<endl;
}
}//这段代码是反常的向下更新向上求和。
}