HDU-1556 Color the ball(前缀数组 || 线段树)

本文介绍使用前缀数组和线段树解决气球涂色问题的方法,通过实例详细解析两种算法的实现过程,展示如何高效统计连续区间内元素的出现次数。

题目:

       N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?

Input

      每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。
当N = 0,输入结束。

Output

       每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。

Sample Input

3
1 1
2 2
3 3
3
1 1
1 2
1 3
0

Sample Output

1 1 1
3 2 1

第一种:前缀数组

        题解:这个题,一眼望去首先想到线段树,但是总感觉只是统计一个数字出现的次数,用线段树有点大材小用,当时比赛就只想到了线段树,但是好久没写过线段树了一直卡住写不出来了。赛后还是看了阳神的题解,于是看到了用前缀数组标记,恍然大悟,其实有点类似于用前缀数组对线段树进行模拟,进行数字个数的统计,只是一个是至左向右,一个是至上而下。对了暴力统计肯定超时,这个不多解释。

      说完废话,入正题,怎么用数组标记一个连续区间内正整数的出现次数呢?从刚刚的废话中可以知道前缀数组就是至左到右的方式进行统计,比如样例给你区间[1, 1][1, 2][1, 3],怎么在数组中对着三个区间进行标记,最后还可以统计出每个数字出现的次数呢?举个例子:

arr[6]     0  1  2  3  4  5
[1,  3]    0  1  0  0  -1   //最后在进行输出时,只需要把前面出现过的次数一直往后累加就行了,直到加到arr[4]这里,以后就都不会加了,这样不就把[1, 3]区间出现的数字的次数都在数组中标记下来了。
这样之后arr[1] = 1  arr[4] = -1

比如样例2:
arr[6]     0  1  2  3  4  5
[1, 1]     0  1  -1
[1, 2]     0   1    -1 
[1, 3]     0   1        -1
arr[1] = 3  arr[2] = -1 ...
最后sum[i] = arr[i] + sum[i - 1];

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<sstream>
#include<vector>
#include<map>
#include<set>
#include<queue>
#define up(i, x, y) for(int i = x; i <= y; i++)
//#define down(i, x, y) for(int i = x; i >= y; i--)
using namespace std;
typedef long long ll;
int n, m, k, s;
int a, b;
int T, t;
int ans = 0;
int arr[100000 + 10];
int x = 1;
int main()
{
    while(~scanf("%d", &n) && n)
    {
        memset(arr, 0, sizeof(arr));
        up(i, 1, n)
        {
            scanf("%d %d", &a, &b);
            arr[a]++;
            arr[b+1]--;  //对区间进行标记
        }
        t = 0;
        up(i, 1, n)
        {
            t += arr[i - 1];  //计算出现次数
            printf("%d%c", t + arr[i], i == n ? '\n' : ' ');
        }

    }
}

第二种:线段树

不了解线段树的推荐看:https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html

线段树思路就比较简单了,技巧性不大,主要通过lazy变量(下面代码中的 f 变量)来节省时间,因为是单一查询,所以用线段树做,就用两个关键操作,一个是区间修改,另一个是单点查询,但是有一点需要注意:因为是多组输入,所以每次线段树初始化时,不要忘记修改lazy变量。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<sstream>
#include<vector>
#include<map>
#include<set>
#include<queue>
#define up(i, x, y) for(int i = x; i <= y; i++)
//#define down(i, x, y) for(int i = x; i >= y; i--)
using namespace std;
typedef long long ll;
int a, b, x , ans, n;
struct node
{
    int l, r, w, f; //f相当于lazy变量,我喜欢用f表示
};
node t[400000 + 10];
/**********************/
//建树

void build(int l, int r, int k)
{
    t[k].l = l; t[k].r = r;
    if(l == r){
        t[k].w = 0;
        return ;
    }
    t[k].f = 0;  //由于是多组输入,记得修改
    t[k].w = 0;

    int m = (l + r) / 2;
    build(l, m, k * 2);
    build(m + 1, r, k * 2 + 1);
    //t[k].w = t[k * 2].w + t[k * 2 + 1].w; //这里只是简单统计,不需要回溯进行状态合并。
}

/******************************/
//区间修改[a, b]

void down(int k)
{
    t[k * 2].f += t[k].f;
    t[k * 2 + 1].f += t[k].f;
    t[k * 2].w += t[k].f * (t[k * 2].r - t[k * 2].l + 1);
    t[k * 2 + 1].w += t[k].f * (t[k * 2 + 1].r - t[k * 2 + 1].l + 1);
    t[k].f = 0;
}

void add(int a, int b, int k)
{
    if(t[k].l >= a && t[k].r <= b){
        t[k].w += (t[k].r - t[k].l + 1) * 1;
        t[k].f += 1;
        return ;
    }
    if(t[k].f) down(k);
    int m = (t[k].l + t[k].r) / 2;
    if(a <= m) add(a, b, k * 2);
    if(b > m) add(a, b, k * 2 + 1);
    //t[k].w = t[k * 2].w + t[k * 2 + 1].w;  //这里只是简单统计,不需要回溯进行状态合并。
}

/******************************/
//单点查询

void ask(int x, int k)
{
    if(t[k].l == x && t[k].r == x)
    {
        ans = t[k].w;
        return ;
    }
    if(t[k].f) down(k);
    int m = (t[k].l + t[k].r) / 2;
    if(x <= m) ask(x, k * 2);
    if(x > m) ask(x, k * 2 + 1);
}

int main()
{
    while(~scanf("%d", &n) && n)
    {
        build(1, n, 1);
        up(i, 1, n)
        {
            scanf("%d %d", &a, &b);
            if(a > b) swap(a, b);
            add(a, b, 1);
        }
        up(i, 1, n)
        {
            ask(i, 1);
            printf("%d%c", ans, i == n ? '\n' : ' ');
        }
    }
}

 

 


 

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值