SCAU-1144 数星星-HDU-1166-树状数组的应用

本文借鉴
代码提供:https://www.cnblogs.com/geek1116/p/5566709.html
树状数组详解:https://www.cnblogs.com/xenny/p/9739600.html
不知道树状数组的同学呢就看看上面的链接啦讲的很棒呢(在学习树状数组的时候其实是可以同时学一下线段树的)
在学习的时候可以看下这个题目,是可以用线段树来做也可以用树状数组来做的https://www.cnblogs.com/M-cag/archive/2012/08/16/2642459.html
核心呢还是这个:2^k = i&(-i)

C[i] = A[i - 2k+1] + A[i - 2k+2] + ... + A[i]

如果是要修改了一个A[]的话会引起其他的c改变的,就要看那些的c[]包含了A[]
A[i] 包含于 C[i + 2k]、C[(i + 2k) + 2k]...
还是打一下基本的代码把(我也练下)
下面用a[]来装原来的数组,然后用c[]来装树状数组的
算2^k的函数是lowbit


int lowbit(int x)//这里的x就是那个k了
{
return x&(-x);
}

如果是修改了A[]的话
void update(int i,int k)//在i位置上加k也就是改变了A[]
{
while(i<=n)//只要还在范围内的话就可以一直的修改了
{
c[i]+=k;
i+=lowbit(i);
}
}

求和的话
int getsum(int i)//求1到i的和
{
int res=0;
while(i>0)
{
res+=c[i];
i-=lowbit(i);
}
return res;
}


1144 数星星
该题有题解

时间限制:564MS 内存限制:65536K
提交次数:193 通过次数:43

题型: 编程题 语言: G++;GCC

 

Description
天文学家们喜欢观察星星。它们把每颗星星看成一个点,并把每颗星星左下方(即横坐标和纵坐标都不比它大)的星星颗数作为它的等级值。
现给出所有星星(星星个数为N)的坐标,计算并输出指定编号的星星的等级。

注意:不存在相同坐标的星星

 


输入格式
第一行为N
后N行为编号为1到N的星星的坐标(坐标用整数)
此后是M
后一行是M个星星的编号

N<=100000
M<=1000

坐标范围0<=x,y<=1000000

 

输出格式
要求依次输出这M个星星的等级,一行一个

 

 

输入样例
5
0 0
2 0
3 0
1 1
2 2
2
4 5

 

 

输出样例
1
3

思路:一般这种输入x y坐标型的,或者一个物体是有两个属性的话,如果要进行比较的话,一帮都是先对其中一个变量进行排序,然后再吧问题变成了对另外一个变量的比较上了
由于如果要对结构体进行比较的话最好还是用到c++来搞,所以下面代码是用c++来实现的了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <queue>
#include <stack>
#include <map>
#include <vector>
#include <set>
#include <utility>
#define ll long long
#define inf 0x3f3f3f3f
#define MAX_N 100000
#define MAX_X 1000000
using namespace std;

typedef struct node
{
    int x,y;
    int p; //因为我们对数组进行了重新的排序,但是最好输入的是下标来看他的等级是多少的,所以为了之后可以输入下标得出结果,
     //就要再定义一个p来存放原来的下标值的
    //所以用个变量p来标记该元素的原下标
} node;
node star[MAX_N+5];//
bool cmp(node a,node b) //按y大小升序来排序,y相同时把x较小的排前面
{
    if(a.y!=b.y)
        return a.y<b.y;
    else
        return a.x<b.x;
}
int n,m,maxn;//n表示的是有多少个星星,然后m是要看多少个星星的等级,用maxn来存放星星里面横坐标最大的那个
int ans[MAX_N+5]; //ans[]数组存储每个星星的等级,这个数组的下标表示的是输入数的下标,而并不是x或者y
int bit[MAX_X+5]; //树状数组中的统计和的数组,也就是那个c数组了
int sum(int pos)//就是板子了呢
{
    int res=0;
    while(pos)
    {
        res+=bit[pos];//再次说bit[]就是板子里面的c[]
        pos-=(pos&-pos);
    }
    return res;
}
void updata(int pos,int value)//由于是以颗数来搞的,所以value其实就是1了,就是在相应的位置上加1的了
{
    while(pos<=maxn)
    {
        bit[pos]+=value;//把只要和pos位置有关的数组都加1,其实就是输入了这个位置说明就有这个点了,所以和这个点有光的所以的bit[]就加1即可了
        pos+=(pos&-pos);
    }
}
int main()
{
    //freopen("input.txt","r",stdin);
    memset(bit,0,sizeof(bit));//这里是没有用到另外一个数组a[]来装原数组的
    scanf("%d",&n);
    maxn=-1;
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d",&star[i].x,&star[i].y);
        star[i].x++;    //注意了:在树状下标不能有0,否则会死循环!
        star[i].y++;   //所以所有的横纵坐标都+1
        star[i].p=i;//就是下标,我们是从1开始到n的作为下标的
        if(star[i].x>maxn)
            maxn=star[i].x;  //更新最大的x坐标
    }
    sort(star+1,star+n+1,cmp);//以y坐标升序来sort
    //
    for(int i=1; i<=n; i++)
    {
        ans[star[i].p]=sum(star[i].x); //计算位于其左下方的星星个数,注意这个ans是以这个点的输入的次序也就是下标来的
        updata(star[i].x,1);  //更新bit[]数组
    }
    //
    scanf("%d",&m);
    while(m--)
    {
        int temp;
        scanf("%d",&temp);
        printf("%d\n",ans[temp]);
    }
    return 0;
}

例题:

敌兵布阵

 HDU - 1166

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 int n,m;
 5 int a[50005],c[50005]; //对应原数组和树状数组
 6 
 7 int lowbit(int x){
 8     return x&(-x);
 9 }
10 
11 void updata(int i,int k){    //在i位置加上k
12     while(i <= n){
13         c[i] += k;
14         i += lowbit(i);
15     }
16 }
17 
18 int getsum(int i){        //求A[1 - i]的和
19     int res = 0;
20     while(i > 0){
21         res += c[i];
22         i -= lowbit(i);
23     }
24     return res;
25 }
26 
27 int main(){
28     int t;
29     cin>>t;
30     for(int tot = 1; tot <= t; tot++){
31         cout << "Case " << tot << ":" << endl;
32         memset(a, 0, sizeof a);
33         memset(c, 0, sizeof c);
34         cin>>n;
35         for(int i = 1; i <= n; i++){
36             cin>>a[i];
37             updata(i,a[i]);   //输入初值的时候,也相当于更新了值,所有就直接的调用update函数即可了
38         }
39 
40         string s;//存放的是指令
41         int x,y;
42         while(cin>>s && s[0] != 'E'){//如果是E的话就结束了
43             cin>>x>>y;//由于只要不是结束的话都是要输入两个数的 x和y的
44             if(s[0] == 'Q'){    //求和操作
45                 int sum = getsum(y) - getsum(x-1);    //x-y区间和也就等于1-y区间和减去1-(x-1)区间和
46                 cout << sum << endl;
47             }
48             else if(s[0] == 'A'){
49                 updata(x,y);//在x的位置上加y
50             }
51             else if(s[0] == 'S'){
52                 updata(x,-y);    //减去操作,即为加上相反数
53             }
54         }
55 
56     }
57     return 0;
58 }
View Code

其他的扩展:
区间更新、单点查询(差分建树)
核心:当某个区间[x,y]值改变了,区间内的差值是不变的,只有D[x]和D[也就是说问题变成了:原来要更新一个区间的值变成了只需要更新两个点y+1]的值发生改变,
所以我们就可以利用这个性质对D[]数组建立树状数组,
也就是说问题变成了只用在原来的基础上吧第x个的加k,然后第y+1个的减k即可了

 1 int n,m;
 2 int a[50005] = {0},c[50005]; //对应原数组和树状数组
 3 
 4 int lowbit(int x){
 5     return x&(-x);
 6 }
 7 
 8 void updata(int i,int k){    //在i位置加上k
 9     while(i <= n){
10         c[i] += k;
11         i += lowbit(i);
12     }
13 }
14 
15 int getsum(int i){        //求D[1 - i]的和,即A[i]值
16     int res = 0;
17     while(i > 0){
18         res += c[i];
19         i -= lowbit(i);
20     }
21     return res;
22 }
23 
24 int main(){
25     cin>>n;27     for(int i = 1; i <= n; i++){
26         cin>>a[i];
27         updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
28     }
29     
30     //[x,y]区间内加上k
31     updata(x,k);    //A[x] - A[x-1]增加k
32     updata(y+1,-k);        //A[y+1] - A[y]减少k
33     
34     //查询i位置的值
35     int sum = getsum(i);
36 
37     return 0;
38 }
View Code

区间更新、区间查询:还是利用了差分的思维了
核心:维护两个数状数组,sum1[i] = D[i],sum2[i] = D[i]*(i-1);

 1 int n,m;
 2 int a[50005] = {0};
 3 int sum1[50005];    //(D[1] + D[2] + ... + D[n])
 4 int sum2[50005];    //(1*D[1] + 2*D[2] + ... + n*D[n])
 5 
 6 int lowbit(int x){
 7     return x&(-x);
 8 }
 9 
10 void updata(int i,int k){
11     int x = i;    //因为x不变,所以得先保存i值
12     while(i <= n){
13         sum1[i] += k;
14         sum2[i] += k * (x-1);
15         i += lowbit(i);
16     }
17 }
18 
19 int getsum(int i){        //求前缀和
20     int res = 0, x = i;
21     while(i > 0){
22         res += x * sum1[i] - sum2[i];
23         i -= lowbit(i);
24     }
25     return res;
26 }
27 
28 int main(){
29     cin>>n;
30     for(int i = 1; i <= n; i++){
31         cin>>a[i];
32         updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
33     }
34 
35     //[x,y]区间内加上k
36     updata(x,k);    //A[x] - A[x-1]增加k
37     updata(y+1,-k);        //A[y+1] - A[y]减少k
38 
39     //求[x,y]区间和
40     int sum = getsum(y) - getsum(x-1);
41 
42     return 0;
43 }
View Code

区间修改、单点查询模板题目:https://www.luogu.org/problem/show?pid=3368

区间修改、区间查询模板题目:https://vjudge.net/problem/POJ-3468
最后再打波广告:https://www.cnblogs.com/xenny/p/9739600.html讲的实在是太好了

猜你喜欢

转载自www.cnblogs.com/SCAU-gogocj/p/11938997.html