TSOJ1350~1352 面朝大海,春暖花开

题目描述:

选择那些大晴天的日子,行走在孤单的海岸线,静静地种花给自己看~

我们假设把海岸线分为n块,每块的分别标记为1...n,每块都可以种花,每次种花可以选择某个[left,right]的闭区间,每块种上一朵花.经过m次种花操作后,根据输入的区间,求该区间内花的总数.

输入描述:

多组输入

对每组输入,第一行有两个整数n m,分别代表总块数和种花的次数.(1 <= n, m <= 100)

接下来的m行, 每行两个整数 L,R 代表[L,R]区间内每块种上一朵花.(1 <= L <= R <= n)

最后一行,输入两个整数 a,b 代表最后要查询的花的总数的区间.(1 <= a <= b <= n)

输出描述:

     对每组测试数据,输出区间[a,b]内花的总数


总共有三题,不同之处在于数据范围不同。
1350:简单模拟,将每一块的花的数量存在一个数组a[n]中,每次读取L和R后将a[L]到a[R]部分每个点都加1。输入最后一行读取a和b,累加法得出ans。


1351:简单模拟,数据范围稍大。与上一题不同,此题需要优先读入每组l和r以及a、b,如果存在某一组l和r满足(r<a||l>b)则跳过此组数据,继续处理下组数据。其余数据处理方法与上一组相同。(没有试过此解法,只是感觉,当时题目看复杂了直接写了树状数组)


1352:树状数组+差分
使用条件:多组更新+多组查询
使用方法:
(1)lowbit运算
         int lowbit(int x)
          {
      return x&(-x);
          }
        lowbit(x)得出的结果为x的二进制数从低位向高位数,仅保留第一个数字1。
举个例子,5的二进制为101,lowbit(5)结果为001,即为1

先列举出从1~32的lowbit,

1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32

我们让第i个位置管理[i-lowbit(i)+1,i]这一段区间,示意图如下:


某个数字的管理位置即为顺着该数字往下划,碰到的第一根横线所覆盖的范围。
举个例子,4能管理1-4的所有数,5只能管理到它自己。
我们每次执行对第X个数+1,即需要对所有能管理到x的数+1。
(2)寻找快速管理到第X个数的方法
void add(int x)
{
     for(int i=x;i<=n;i+=lowbit(i))
        a[i]++;
}
假设X=3,那么首先把a[3]++;
其次,此时X = 3+lowbit(3)= 4,对照上图,4能管理到3;
依次类推,X = 4+lowbit(4)= 8……
一直到x>n为止。
该操作将时间复杂度有O(1)上升到O(logn),得来的好处是将下面查询操作的时间复杂度 O(n)级别降低到O(logn)级别。
(3)查询操作
首先需要明确的是,查询的并不是具体区间和,而是前缀和。
int sum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans += a[i];
return ans;
}
此操作即为求sum(1,x),lowbit的巧妙之处在此体现出来——不会出现重复加上某一个数,具体可以参照上图并自己思考一下。

以上为树状数组的大致讲解,单点查询的话用上述方法即可。而此题有些神奇,考的是区间查询,需要引入差分数组来解决。即区间[L,R]所有值+1改成"位置L加上1,位置R+1减去1",sum(a,b)即为 sum(1,b)- sum(1,a-1)
底下方法源于 http://blog.csdn.net/fsahfgsadhsakndas/article/details/52650026 。
需要修改lowbit函数和sum函数,
void add(int x,int num)
{
for(int i=x;i<=n;i+=lowbit(i))
{
c1[i] += num;
c2[i] += num*x;
}
}
int sum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
{
ans += (x+1)*c1[i]-c2[i];
}
return ans;
}

我们假设sigma(r,i)表示r数组的前i项和,调用一次的复杂度是log2(i)

设原数组是a[n],差分数组c[n],c[i]=a[i]-a[i-1],那么明显地a[i]=sigma(c,i),如果想要修改a[i]到a[j](比如+v),只需令c[i]+=v,c[j+1]-=v。

观察式子:
a[1]+a[2]+...+a[n]

= (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) 

= n*c[1] + (n-1)*c[2] +... +c[n]

= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])    (式子①)

那么我们就维护一个数组c2[n],其中c2[i] = (i-1)*c[i]

每当修改c的时候,就同步修改一下c2,这样复杂度就不会改变

那么

式子①

=n*sigma(c,n) - sigma(c2,n)

于是我们做到了在O(logN)的时间内完成一次区间和查询。



以上即为树状数组的大致题解,其中最为精彩的地方就在于lowbit函数的运用以及差分求前缀和,巧妙地将算法的时间复杂度由O(N)级别降到O(logn)级别。





阅读更多
版权声明:版权所有,转载请标明 https://blog.csdn.net/zxwsbg/article/details/78534766
文章标签: 算法 acm 树状数组
个人分类: 数据结构
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭