2017暑假训练第十五天

  既然不用做题了,就细细的理解一下知识点,决定学一点写一点,写点有用的东西。

  首先是看了一下用树状数组求逆序数,看了好多版本,有直接求算的,有用结构体存取,再排序后计算的,但是总体的思路都是一样的,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

题意:
N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?
  这个题目就是简单的处理,一个区间的所有元素加1,问最后加了几次,看起来用线段树更加省时间的算法最后竟然不如树状数组的算法,代码如下:
#include<iostream>
#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;
        }
    }//这段代码是反常的向下更新向上求和。       
}
代码的主体没变,只有上面两段的应用方式和之前提到的有所不同。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值