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;
}
其实也算不上规划,但确实有一些动态思想