HDU杭电 1556 (fjutacm 1247) Color the ball 线段树更新区间&动态规划?

Problem Description

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个气球总共被涂色的次数。

SampleInput

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

SampleOutput

1 1 1 
3 2 1

这题其实有两种解法,第一种就是线段树解法,用线段树去更新区间。因为是用的二分思想,所以时间复杂度差不多在O(log(2,n)),不太会算。难就难在结点跟其所代表的区间不是一回事,结点只是用来标记一段区间。我直接上代码注释和图。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <iostream>
#include <string.h>
#include <string>
const int MAX=1e6+5;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

struct node
{
  int times, l, r;
}tree[MAX];

/// dfs原理
void build(int v, int l, int r)/// v是当前结点,一开始一定是从1开始;l、r是真
{                              ///正意义上的左右边界,一开始一定是1和N
  tree[v].l = l;/// 当前结点记录下当前区间
  tree[v].r = r;
  tree[v].times = 0;  /// 所有气球(结点)被涂色的次数初始化为0
  if (l==r) /// 当前结点记录的左右边界重合,说明是叶子结点,return
    return;
  int m=(l+r)>>1;
  build(v<<1, l, m);    /// 建左子树
  build(v<<1|1, m+1, r);/// 建右子树,PS.|1的操作是二进制异或1,二进制最后一位
                        ///为0时即+1
}/// 建图是dfs的思路,从最左边的叶子结点遍历到最右边的,保证会完整地将整个区
 ///间处理完

这里附一张线段树的概念图,数字是结点编号,会发现左右儿子结点刚好分别是父节点的2倍和2倍+1。

void update(int v, int l, int r)/// 更新区间,v还是传入根节点1,然后是所要更新
{                               ///的区间[l,r]。这里是在收缩整个区间,来查找目
                                ///标区间
  if (tree[v].l>=l&&tree[v].r<=r) /// 目标区间刚好被当前结点所代表区间包含在内
  {                               ///时,当前结点的times++,其实这里times的用
    tree[v].times ++;             ///法更类似于lazy,我们不具体到每一个气球的
                                  ///涂色,而是在之后遍历的时候把times逐层下放
    return;                       ///所以这里直接return,不再往下递归
  }
  int m=(tree[v].l+tree[v].r)>>1; /// 这里开始,就是二分的基本操作
  if (r<=m)
    update(v<<1, l, r);
  else if (l>m)
    update(v<<1|1, l, r);
  else  /// 这里是目标区间跨区间的情况,比较特殊,要把它分成两半分开递归
  {
    update(v<<1, l, m);
    update(v<<1|1, m+1, r);
  }
}/// 更新区间时,首先,找到目标区间的操作就是二分的思想;然后数据更新用的是
 ///类似于记录lazy、之后下放的方法,后面我会找一些更针对lazy的题目写写

int ans[MAX], tmp;
void answer(int v) /// 最终把整棵树整合成我们要的答案,依然是dfs的方式遍历
{
  if (tree[v].l==tree[v].r)/// 到叶子结点,就说明是一个具体的气球
  {
    ans[tmp++] = tree[v].times;/// 因为是dfs遍历,所以可以直接按找到的先后顺序
                               ///放入答案数组
    return;        /// 没有向下递归的必要了
  }
  tree[v<<1].times += tree[v].times;  /// 只要当前结点还代表一个区间,就把times
  tree[v<<1|1].times += tree[v].times;///下放,因为这个结点的times,代表着有过
                                      ///多少个被涂色的区间,包含或刚好等于当
                                      ///前这个结点代表的区间,并给这个区间下
                                      ///的所有气球上过色
  answer(v<<1);  /// 遍历左区间
  answer(v<<1|1);/// 遍历右区间
}

int main()
{
  int N, a, b, i, j;
  while (cin>>N&&N)
  {
    memset(tree, 0, sizeof(tree));
    memset(ans, 0, sizeof(ans));
    build(1, 1, N); /// 建图从根结点1开始,遍历整个区间
    for (i=1; i<=N; i++)
    {
      scanf("%d%d", &a, &b);
      update(1, a, b);  /// 更新也从根结点1开始,找目标区间
    }
    tmp = 0;
    answer(1);
    for (i=0; i<N; i++)
      if (i) printf(" %d", ans[i]); /// 注意这题有输出格式
      else printf("%d", ans[i]);
    printf("\n");
  }
  return 0;
}

第二种解法我个人觉得很厉害,不是一种算法,我认为是一种动态规划思想。不是我自己想出来的,参考了很多大佬的题解。这种解法也是先进行简单的预处理,之后一次遍历整合出答案,整体复杂度O(n),但是代码很简洁,思路很清晰。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <iostream>
#include <string.h>
#include <string>
const int MAX=1e6+5;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int balloon[MAX];
int main()
{
  int N, i, j, a, b;
  while (cin>>N&&N)
  {
    memset(balloon, 0, sizeof(balloon));
    for (i=1; i<=N; i++)
    {
      scanf("%d%d", &a, &b);
      balloon[a] ++;  /// 因为是区间涂色,这里先在坐边界a处++,代表从a开始往
      balloon[b+1] --;///后涂;b+1处--代表到b+1处就不涂了,为后续处理作铺垫。
      /// 这里下标可能会超过N,所以开数组的时候记得开大点。
    }
    for (i=1; i<=N; i++)
    {
      balloon[i] += balloon[i-1];/// 这里可能需要模拟一下才能理解。比如有4个气
      ///球,我给1-3上色,1处++,4处--,其他保持为0。然后这里往后算前缀和,上
      ///色区间内的气球没有--过,所以会一直继承前一位的涂色状态,一直到4为止,
      ///包括4以后的位置,就都会因为4处--过,所以不会被算作上过色。
      /// 也就是说,不论涂色区间有多少个、互相怎么交错,单一的一次涂色只会在
      ///从a开始到b的相应区间生效+1,到b+1处就会被相应的一次--抹除掉。
      if (i==N) printf("%d\n", balloon[i]);
      else printf("%d ", balloon[i]);      /// 还是要记得这题有输出格式要求
    }
  }
  return 0;
}

其实也算不上规划,但确实有一些动态思想

发布了19 篇原创文章 · 获赞 0 · 访问量 506

猜你喜欢

转载自blog.csdn.net/qq_43317133/article/details/99290268