欢迎光临
我们一直在努力

清点人数(信息学奥赛一本通- P1538)

【题目描述】

NK 中学组织同学们去五云山寨参加社会实践活动,按惯例要乘坐火车去。由于 NK 中学的学生很多,在火车开之前必须清点好人数。

初始时,火车上没有学生。当同学们开始上火车时,年级主任从第一节车厢出发走到最后一节车厢,每节车厢随时都有可能有同学上下。年级主任走到第 m 节车厢时,他想知道前 m 节车厢上一共有多少学生,但是他没有调头往回走的习惯。也就是说每次当他提问时,m 总会比前一次大。

【输入】

第一行两个整数 n,k,表示火车共有 n 节车厢以及 k 个事件。

接下来有 k 行,按时间先后给出 k 个事件,每行开头都有一个字母 A,B 或 C。

如果字母为 A,接下来是一个数 m,表示年级主任现在在第 m 节车厢;

如果字母为 B,接下来是两个数 m,p,表示在第 m 节车厢有 p 名学生上车;

如果字母为 C,接下来是两个数 m,p,表示在第 m 节车厢有 p 名学生下车。

学生总人数不会超过 105 。

【输出】

对于每个 A ,输出一行,一个整数,表示年级主任的问题的答案。

【输入样例】

10 7
A 1
B 1 1
B 3 1
B 4 1
A 2
A 3
A 10

【输出样例】

0
1
2
3

【提示】

数据范围与提示:

对于 30% 的数据,1≤n,k≤10^4 ,至少有 3000 个 A;

对于 100% 的数据,1≤n≤5×10^5,1≤k≤10^5 ,至少有 3×10^4 个 A。

一、 题目分析

【题目模型】 一列火车有 n 节车厢,初始全空。有 k 次动态事件交替发生:

  • 事件 B/C: 第 m 节车厢有 p 名学生上车(增加)或下车(减少)。

  • 事件 A: 年级主任走到第 m 节车厢,提问:前 m 节车厢(即区间 [1,m])一共有多少学生?

【数据规模】 车厢数 n≤5×10^5,事件数 k≤10^5。 如果在 1s 的时限内使用 O(n) 的暴力累加来回答主任的提问,当查询次数逼近 10^5 时,总运算量将高达 5×10^10,必定超时。我们需要一种支持 O(logn) 级别动态修改和查询的数据结构。

二、 思考过程

题目中有一句非常耐人寻味的话:

“他没有调头往回走的习惯。也就是说每次当他提问时,m 总会比前一次大。”

萌新的直觉: 很多同学看到这句话,第一反应是:“既然主任一直往前走,那我是不是不需要复杂的数据结构?只要拿一个变量 sum,主任走到哪,我就一路加过去就行了?”

破局点: 不行!请注意题目的另一句话:“每节车厢随时都有可能有同学上下”。 主任虽然一直在往右走(查询的右端点单调递增),但是学生完全有可能在他已经走过去的、身后的车厢里上下车!如果只用一个标量记录,身后的动态修改就彻底丢失了。

这正是必须使用树状数组的原因:我们需要一个能够随时响应任意位置的修改,并能极速求出动态前缀和的武器。

三、 解题思路与算法设计

抛开故事外衣,这就是一个长度为 n 的全零数组,支持以下三种操作:

  • 核心引擎 lowbit: lowbit(x)=x&(-x),用于提取x二进制最低位的1所代表的整数,是树状数组向上汇报和向下查询的跳跃步长。

  • 学生上车/下车(单点修改):

    • 调用update(m, p)表示第m节车厢增加p人。

    • 调用update(m, -p)表示第m节车厢减少p人。

    • 物理操作:顺着 i+=lowbit(i),将所有管辖了该车厢的“上级节点”全部同步修改。

  • 主任提问(前缀查询):

    • 调用query(m)。

    • 物理操作: 顺着 x-=lowbit(x),将多个互不重叠的管辖区间的总人数累加,瞬间拼凑出前m节车厢的总人数。

  • 四、 时空复杂度分析

    • 时间复杂度: 每次update和query都在树状数组的层级上跳跃,单次耗时O(logn)。总共有k 个事件,总体时间复杂度为O(klogn)。对于10^5的操作量,耗时不到数毫秒,极速 AC。

    • 空间复杂度: 仅需开辟一个长度为 n+1 的树状数组 c,总体空间复杂度为 O(n)。

    五、 易错点总结

  • 极其健壮的字符读入: 题目每行以字母 A, B, C 开头。实战中极度不建议使用 scanf("%c") 或 gets,因为老旧评测机的行末常常带有隐形的回车符 \\r\\n,极易导致读入错位。标准解法: 使用 char thing; cin >> thing;,C++ 的 cin 会自动安全地过滤掉所有空白字符(包括空格和换行)。

  • 局部变量的整洁性: 在 if-else 分支中按需声明局部变量 int m, p;,用完即毁,防止变量污染外层逻辑。

  • 全员long long: 尽管题目提示学生总人数不超过10^5,但在算法竞赛中,只要遇到“区间求和”、“树状数组”,一律使用long long声明底层数组和返回值,这是百利而无一害的好习惯。

  • 六、 完整代码

    //单点修改 区间查询
    #include <iostream>
    using namespace std;
    typedef long long ll;
    int n,k;
    ll c[500010];//树状数组

    //返回x二进制表示下最低位1所代表的整数
    int lowbit(int x){
    return x&(-x);
    }

    //树状数组更新操作 原第x节车厢增加val学生
    //对应树状数组所有包含x的项都增加val学生
    void update(int x,ll val){
    for(int i=x;i<=n;i+=lowbit(i))
    c[i]+=val;
    }

    //查询前x节车厢总共多少学生
    ll query(int x){
    ll ret=0ll;//存前缀和
    while(x){//c[x]包含了多节车厢
    ret+=c[x];
    //减去c[x]包含的项数,精准跳跃到上一个相邻不重叠的管辖区间
    x-=lowbit(x);
    }
    return ret;
    }

    int main(){
    //io加速
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>k;
    //总共k个事件
    while(k–){
    char thing;//事件
    cin>>thing;
    if(thing=='A'){//表示年级主任现在在第m节车厢
    int m;
    cin>>m;
    cout<<query(m)<<"\\n";
    }
    //表示在第m节车厢有p名学生上车
    else if(thing=='B'){
    int m,p;
    cin>>m>>p;
    update(m,p);
    }
    //表示在第m节车厢有p名学生下车
    else{
    int m,p;
    cin>>m>>p;
    update(m,-p);
    }
    }
    return 0;
    }


    赞(0)
    未经允许不得转载:171主机测评 » 清点人数(信息学奥赛一本通- P1538)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址