zoukankan      html  css  js  c++  java
  • 牛客小白月赛12 F 华华开始学信息学 (分块+树状数组)

    链接:https://ac.nowcoder.com/acm/contest/392/F
    来源:牛客网

    时间限制:C/C++ 2秒,其他语言4秒
    空间限制:C/C++ 32768K,其他语言65536K
    64bit IO Format: %lld

    题目描述

    因为上次在月月面前丢人了,所以华华决定开始学信息学。十分钟后,他就开始学树状数组了。这是一道树状数组的入门题:
    给定一个长度为N的序列A,所有元素初值为0。接下来有M次操作或询问:
    操作:输入格式:1 D K,将ADAD加上K。
    询问:输入格式:2 L R,询问区间和,即Ri=LAi∑i=LRAi。
    华华很快就学会了树状数组并通过了这道题。月月也很喜欢树状数组,于是给华华出了一道进阶题:
    给定一个长度为N的序列A,所有元素初值为0。接下来有M次操作或询问:
    操作:输入格式:1 D K,对于所有满足1iN1≤i≤N且i0(modD)i≡0(modD)的i,将AiAi加上K。
    询问:输入格式:2 L R,询问区间和,即Ri=LAi∑i=LRAi。
    华华是个newbie,怎么可能会这样的题?不过你应该会吧。

    输入描述:

    第一行两个正整数N、M表示序列的长度和操作询问的总次数。
    接下来M行每行三个正整数,表示一个操作或询问。

    输出描述:

    对于每个询问,输出一个非负整数表示答案。
    示例1

    输入

    复制
    10 6
    1 1 1
    2 4 6
    1 3 2
    2 5 7
    1 6 10
    2 1 10

    输出

    复制
    3
    5
    26

    备注:

    1N,M1051≤N,M≤105,1DN1≤D≤N,1LRN1≤L≤R≤N,1K1081≤K≤108


    解题思路:首先对于询问区间和我们可以很容易想到需要建立一个树状数组,但是对于更新操作却有点棘手,对于所有满足1iNi≡0(modD)的i,将Ai加上K,即需要使下标为D的倍数的数加上a[i],而这些数基本遍布整个区间[1,n],特别是当D很小时,复杂度越高。
    但是我们可以发现对于D比较小的情况,这类情况也比较有限。比如当D大于sqrt(n)时,我们所要更新的数字的个数也最多也不超过sqrt(n),而对于这部分数据我们可以直接暴力更新就好了,而当D小于等于sqrt(n),我们可以用一个标记数组lazy[D]累计起来,标记

    lazy[D]+=k,这样我们就相当于把区间[1,n]的任意下标x如果x<sqrt(n)把其和其在区间中的倍数看成一个块,当我们查询区间和sum[l,r]时我们便可以用当前区间和(使用树状数组ask(r)-ask(l-1)),加上各个块在该区间的个数(r-l+1)乘以该块的标记值(lazy数组值)。
    例如:
    n=12
    下标: 1 2 3 4 5 6 7 8 9 10 11 12
    值: 2 1 7 14 6 4 1 7 10 3 7 9

    块数目sqrt(n)=3
    把下标为1的倍数看成是一块,即下标为1 2 3 4 5 6 7 8 9 10 11 12的数
    把下标为2的倍数也看成是一块,即下标为 2 4 6 8 10 12的这几个数
    把下标为3的倍数也看成是一块,即下标为3 6 9 12的这几个数
    当D的值大于3时,如D=5,k=1,我们就直接暴力更新各个值更新下标为5的值和下标为10的值变成:
    下标: 1   2   3   4   5   6   7   8  9   10  11   12
    值:   2   1   7  14   7  4   1   7  10   4   7   9
    而当D=2,k=1时,我们选择不更新数组,而是直接用lazy数组标记,使得lazy[2]+=1,它表示的是2的倍数这一整块数都需要加上1
    然后当我们询问区间和时,如询问区间[3,10]的和时,我们先用树状数组求数组在该区间的和sum=7+14+7+4+1+7+10+4=54;
    然后我们遍历每一个块,看是否含有标记值,如果有,则加上该块的标记值*该块在查询区间内的个人。
    在例子中,lazy[1]为0(1的倍数并未被标记),表示无标记,则跳过;
    当遍历到2的倍数这一块时,因为lazy[i]=1含有值,所以我们需要使sum+=lazy[2]+(10/2-3/2)(其中10/2-3/2表示区间[3,10]中2的倍数的个数)
    而当遍历到3的倍数时,因为lazy[3]=0所以也会跳过,这样我们便得出了结果。

    代码:
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6;
    int n,m;
    ll sum[maxn],lazy[maxn];
    int lowbit(int x){return x&(-x);}
    void add(int x,ll val){
        for(int i=x;i<=n;i+=lowbit(i))
            sum[i]+=val;
    }
    ll ask(int x){
        ll res=0;
        for(int i=x;i;i-=lowbit(i))
            res+=sum[i];
        return res;
    }
    int main(){
        scanf("%d%d",&n,&m);
        int block=sqrt(n);
        while(m--){
            int id,x,y;
            scanf("%d%d%d",&id,&x,&y);
            if(id==1){
                if(x>block)
                    for(int i=x;i<=n;i+=x)add(i,y);  //想较大,暴力更新下标为x的倍数的值
                else lazy[x]+=y;  //x值较小,用数组标记x的倍数这个块
            }
            else{
                ll ans=ask(y)-ask(x-1);
                for(int i=1;i<=block;i++)
                    if(lazy[i])  //如果这个块有标记值,加上该标记值
                        ans+=(y/i-(x-1)/i)*lazy[i];
                printf("%lld
    ",ans);
            }
        }
        return 0;
    }
  • 相关阅读:
    Python的简单介绍/解释器/变量/变量的数据类型/用户交互及流程控制(if)
    变量
    hello python
    页面默认值显示
    java 获取时间区间
    java Set 中 removeAll 与 addAll
    命名的数据库实例
    linux 打包与解包
    缓存工作原理
    java 格式化日期(转)
  • 原文地址:https://www.cnblogs.com/zjl192628928/p/10544105.html
Copyright © 2011-2022 走看看