hdu 1556 Color the ball
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556
题目大意:
N个气球排成一排,从左到右依次编号1,2,3...N。要给气球涂N次色,每次涂色范围均为一个连续的区间。最后,要问每个气球被涂了多少次色。
思路:
成段更新 、区间求和
看明题意,最直接的想法就是用线性数组做加法,最后统计。总的时间复杂度为O(N^2)+ O(N) 即 O(N^2)。而N的取值范围可到10,0000. 时间限制为3000ms. 果断超时。
然,使用线段树可以很好地解决这个问题。
这样就产生了如下两个问题:
① 怎么涂色呢 ?
当我们给区间[fr,to)中的气球涂色的时候。我们可以利用树递归,从上至下,最后落实到树底的每一个气球,并将气球节点中的值加1。但是 !其实不一定要递归到底层。只要将树中特定的节点加1即可。那么是哪些节点呢 ?没错,就是每次递归过程中,节点对应区间能被 [fr,to)包含的第一个节点。
② 怎么查询气球的涂色次数呢 ?
由于我们只是对特定的一些节点涂色,没有专门为最底层的气球节点涂色。所以在查询每个气球的涂色次数的时候,我们需要递归线段树—从树根 到 气球节点。这时候,你会发现,所有对气球i涂色操作中加1的节点都处于 从树根到气球i的这条路径 上。因此,这条路径上节点的总和就是气球i被涂色的次数!
画出样例2中 线段树的状态如图1所示:
算法步骤:
① 初始化线段树
② add函数为N个区间的气球涂色
③ query 函数 查询N个气球的涂色次数
算法复杂度:
① O(N)
② O(NlogN)
③ O(NlogN)
总的复杂度为O(NlogN)
codes:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int N, n;
const int maxn = 1<<17;
int dat[2*maxn-1];
void init() {
n = 1;
while( n < N )
n *= 2;
for(int i = 0 ; i < 2*n - 1 ; i ++)
dat[i] = 0;
}
// fr,to 为查询范围 每次加一的操作只对树中的一个节点进行。这个
// 节点 的区间范围 就是 刚好能包含 区间 [fr,to) 的那个区间,从树根往下,一定能找到唯一的这样一个节点
void add(int fr , int to , int l , int r , int k) {
if(fr <= l && to >= r)
{
dat[k] ++;
return;
}
else if(to <= l || fr >= r)
return;
else
{
add(fr,to, l, (l+r)/2 , 2*k+1);
add(fr,to, (l+r)/2, r , 2*k+2);
}
}
// 从树根往下递归直到 [fr,to)对应的点,所经过的路径的 所有节点 的 值的总和 就是 该点被涂过的次数
int query(int fr , int to , int l , int r , int k) {
// 不相交
if(to <= l || fr >= r)
return 0;
// 到了尽头
if(fr <= l && to >= r)
return dat[k];
else
return dat[k] + query(fr,to,l,(l+r)/2,2*k+1) + query(fr,to,(l+r)/2,r,2*k+2);
}
int main() {
while(scanf("%d",&N) != EOF)
{
if( N == 0)
break;
init();
int fr,to;
for(int i = 0 ; i < N ; i ++)
{
scanf("%d%d",&fr,&to);
add(fr-1,to,0,N,0);
}
printf("%d",query(0,1,0,N,0));
for(int i = 1 ; i < N ; i ++)
printf(" %d",query(i,i+1,0,N,0));
printf("\n");
}
return 0;
}
运行结果:Accept
Time : 781 MS
Memory: 1256 K