敌兵布阵 I Hate It Balanced Lineup

敌兵布阵

敌兵布阵
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.

第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令

对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

input

1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End 

output

Case 1:
6
33
59

思路

阅读理解后就是一个线段树/树状数组模板题, 区间求和+单点更新。
线段树理解起来不是很难, 主要算法实现起来比较复杂, 对于这点可以通过多写几遍来克服, 很多地方都是应用之前学过的算法比如二分, 递归等等。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10, M = 4 * N;

int n, m;
int a[N];
int tree[M];
// 主要应用递归的思想
void build(int node, int start, int end) // 建树
{
    if (start == end) // 二分搜索到该点之后就返回上一层
    {
        tree[node] = a[start];
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    build(left, start, mid); // 分治求解子节点
    build(right, mid + 1, end);
    tree[node] = tree[left] + tree[right]; // 子节点设置好值之后就把父节点设置为他俩的和
}

void update(int node, int start, int end, int idx, int val)
{
    if (start == end) // 二分找到子节点之后更新
    {
        tree[node] = val;
        a[idx] = val;
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    if (start <= idx && mid >= idx) // 如果idx在二分的左半边就去左半边找
        update(left, start, mid, idx, val);
    else
        update(right, mid + 1, end, idx, val);// 否则去右半边
    tree[node] = tree[left] + tree[right]; // 同建树一样更新父节点的值
}

int query(int node, int start, int end, int L, int R)
{
    if (start > R || end < L) // 如果二分后的区间不在要求的区间内说明该点的值对结果无影响, 返回0即可
        return 0;
    if (start >= L && end <= R) // 若在区间内疚直接范围, 不必再去计算子节点的值, 因为该节点本身就是子节点的和
        return tree[node];
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    int res = 0;
    res += query(left, start, mid, L, R); // 这两句也可以加一个判定, 这样的话开头的判定就不用写了
    res += query(right, mid + 1, end, L, R);
    return res;
}

int main()
{
    int T, kase = 0;
    cin >> T;
    while (T--)
    {
        cin >> n;
        memset(tree, 0, sizeof tree);
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        build(0, 0, n - 1);
        char s[10];
        printf("Case %d:\n", ++kase);
        while (scanf("%s", &s) == 1)
        {
            if (s[0] == 'E')
                break;
            int A, B;
            scanf("%d %d", &A, &B);
            if (s[0] == 'A') // 注意我们是从 0 开始建树, 输入的下标都要-1
                update(0, 0, n - 1, A - 1, B + a[A - 1]);
            if (s[0] == 'Q')
                printf("%d\n", query(0, 0, n - 1, A - 1, B - 1));
            if (s[0] == 'S')
                update(0, 0, n - 1, A - 1, a[A - 1] - B);
        }
    }

    return 0;
}

I Hate It

I Hate It
 很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。

本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。

对于每一次询问操作,在一行里面输出最高成绩。
input

5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

output

5
6
5
9

思路

线段树的节点可以记录很多信息, 上一题我们是记录子树之和, 这一题便可以记录子树之中最大值。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10, M = 4 * N;

int n, m;
int a[N];
int tree[M];

void build(int node, int start, int end)
{
    if (start == end)
    {
        tree[node] = a[start]; // 就他一个所以最大值就是他
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    build(left, start, mid);
    build(right, mid + 1, end);
    tree[node] = max(tree[left], tree[right]); // 这里是最大值
}

void update(int node, int start, int end, int idx, int val)
{
    if (start == end)
    {
        tree[node] = val;
        a[idx] = val;
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    if (start <= idx && mid >= idx)
        update(left, start, mid, idx, val);
    else
        update(right, mid + 1, end, idx, val);
    tree[node] = max(tree[left], tree[right]);
}

int query(int node, int start, int end, int L, int R)
{
    if (start > R || end < L)
        return -2e9; // 注意初始化为极小值, 好习惯
    if (start >= L && end <= R)
        return tree[node];
    if (start == end)
        return tree[node];
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    int res = -2e9; // 注意初始化为极小值, 好习惯
    res = max(query(left, start, mid, L, R), res); // 一样
    res = max(query(right, mid + 1, end, L, R), res);
    return res;
}

int main()
{
    while (cin >> n >> m)
    {
        memset(tree, 0, sizeof tree);
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        build(0, 0, n - 1);
        while (m--)
        {
            char s[2];
            int A, B;
            scanf("%s %d %d", &s, &A, &B);
            if (s[0] == 'Q')
                printf("%d\n", query(0, 0, n - 1, A - 1, B - 1));
            if (s[0] == 'U')
                update(0, 0, n - 1, A - 1, B);
        }
    }

    return 0;
}

Balanced Lineup

Balanced Lineup
可怜的DQgg,因为没按时回校,导致他被困在家乡。
郁闷的DQgg决定在家乡养牛,他拥有N头牛牛(1≤N≤50,000头),他让这些牛牛排成一列。一天,DQgg决定和几头牛一起出门。为了简单起见,他将从这一列中挑选一系列连续的牛牛出来。然而,为了让所有的出门的牛牛都能玩得开心,它们的身高不应该相差太多。
DQgg列出了Q(1≤Q≤200,000)头牛牛的身高(1≤身高≤1,000,000)。对于每一组,他希望您确定组中最矮和最高的牛牛之间的身高差。

第一行:两个空格分隔的整数N和Q
第二行到第N+1行:第i+1行包含一个整数,表示第i头牛牛的高度
第N+2行到第N+Q+1行:每行包含空格隔开的两个整数A、B(1≤A≤B≤N),表示牛牛从A到B的范围

每行包含一个整数,是对每组范围的回答,表示范围内最高和最矮牛牛之间的高度差

input

6 3  
1  
7  
3  
4  
2  
5  
1 5  
4 6  
2 2

output

6  
3  
0

思路

同样是节点记录信息, 这里是记录当前区间的最大值和最小值, 父节点的最大值则由两个子节点的最大值取最大值, 父节点的最小值由两个子节点的最小值取最小值。
我这里图方便用pair<int,int> 实际上还可以用 结构体 或者 另开一个表示 max, min 的数组来表示。

pair版本耗的内存是 另开数组的4倍。
Status - Virtual Judge (csgrandeur.cn)
数组版本:

结构体版本(学长用了位运算优化, 即将* /变为<<1 >>1 ):

pair版本:

另外这题也能用DP来写。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10, M = 4 * N;
typedef pair<int, int> PII;
int n, m;
int a[N];
PII tree[M];

void build(int node, int start, int end)
{
    if (start == end)
    {
        tree[node] = PII(a[start], a[start]); // POJ 不支持 {1,2}
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    build(left, start, mid);
    build(right, mid + 1, end);
    // 这里有变化
    tree[node].first = max(tree[left].first, tree[right].first);
    tree[node].second = min(tree[left].second, tree[right].second);
}
/* // 并不需要更新
void update(int node, int start, int end, int idx, int val)
{
    if (start == end)
    {
        tree[node] = PII(val, val);
        a[idx] = val;
        return;
    }
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
    if (start <= idx && mid >= idx)
        update(left, start, mid, idx, val);
    else
        update(right, mid + 1, end, idx, val);
    tree[node].first = max(tree[left].first, tree[right].first);
    tree[node].second = min(tree[left].second, tree[right].second);
}*/

PII query(int node, int start, int end, int L, int R)
{
    if (start > R || end < L)
        return PII(-2e9, 2e9);
    if (start >= L && end <= R)
        return tree[node];
    int mid = start + end >> 1;
    int left = node * 2 + 1, right = node * 2 + 2;
	// 这里有变化
    PII res = PII(-2e9, 2e9);
    // 算左区间
    PII temp = query(left, start, mid, L, R);
    res.first = max(res.first, temp.first);
    res.second = min(res.second, temp.second);
	// 算右区间
    temp = query(right, mid + 1, end, L, R);
    res.first = max(res.first, temp.first);
    res.second = min(res.second, temp.second);

    return res;
}

int main()
{
    cin >> n >> m;
    memset(tree, 0, sizeof tree);
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    build(0, 0, n - 1);
    while (m--)
    {
        int A, B;
        scanf("%d %d", &A, &B);
        PII t = query(0, 0, n - 1, A - 1, B - 1);
        printf("%d\n", t.first - t.second);
    }

    return 0;
}

posted @ 2022-07-28 16:05  EdwinAze  阅读(67)  评论(0编辑  收藏  举报