【一百零一】【算法分析与设计】差分,1109. 航班预订统计,P4231 三步必杀,P5026 Lycanthropy

1109. 航班预订统计

这里有 n 个航班,它们分别从 1n 进行编号。

有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [first(i), last(i), seats(i)] 意味着在从 first(i)last(i)包含 first(i)last(i) )的 每个航班 上预订了 seats(i) 个座位。

请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

示例 1:

输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 输出:[10,55,45,25,25] 解释: 航班编号 1 2 3 4 5 预订记录 1 : 10 10 预订记录 2 : 20 20 预订记录 3 : 25 25 25 25 总座位数: 10 55 45 25 25 因此,answer = [10,55,45,25,25]

示例 2:

输入:bookings = [[1,2,10],[2,2,15]], n = 2 输出:[10,25] 解释: 航班编号 1 2 预订记录 1 : 10 10 预订记录 2 : 15 总座位数: 10 25 因此,answer = [10,25]

提示:

  • 1 <= n <= 2 * 10(4)

  • 1 <= bookings.length <= 2 * 10(4)

  • bookings[i].length == 3

  • 1 <= first(i) <= last(i) <= n

  • 1 <= seats(i) <= 10(4)

1.

差分,arr数组,需要完成一些操作,例如区间[l,r]加c操作.

将arr数组进行一系列的add操作,add(l,r,c)表示区间[l,r]统一加上c.

2.

add(l,r,c)操作,只需要arr[l]+=c,arr[r+1]-=c.

3.

进行一系列的add操作后,求arr的前缀和,结果是我们想要的答案.

4.

将arr数组中0~3区间加上1,2~5区间加上2,3~6区间加上-1.

很容易可以得到答案是1,1,3,2,1,1,-1.

利用差分,步骤是arr[0]+=1,arr[3+1]-=1.

arr[2]+=2,arr[5+1]-=2.

arr[3]+=-1,arr[6+1]-=-1.

然后arr求前缀和,得到的就是答案.

5.

对arr数组进行操作的意义是,+=表示添加影响,-=表示影响消失.

例如0~3区间加上1,arr[0]+=1表示从下标0开始都要加上1,开始影响.

以直到3+1=4下标影响结束,所以需要-=1.

6.

前缀和计算出影响的结果.

代码

 
// #define DEBUG
#ifdef DEBUG
#define BUG(code) do{code}while(0)
#else
#define BUG(code) do{}while(0)
#endif

class Solution {
public:
    vector<int> ret; // 用于存储最终的航班预订座位数
    vector<vector<int>> bookings; // 存储航班预订记录
    int n; // 航班数量
    vector<int> arr; // 差分数组,用于记录区间加减操作

    void solve() {
        ret.assign(n, 0); // 初始化结果数组,长度为n,初始值为0
        arr.assign(n + 1, 0); // 初始化差分数组,长度为n+1,初始值为0
        for (auto& x : bookings) { // 遍历每条预订记录
            int left = x[0] - 1, right = x[1] - 1, nums = x[2]; // 提取预订记录中的起始航班、结束航班和预订座位数
            arr[left] += nums; // 在差分数组中起始位置加上预订座位数
            arr[right + 1] -= nums; // 在差分数组中结束位置的下一个位置减去预订座位数
        }
        for (int i = 0; i < n; i++) { // 计算前缀和得到每个航班的预订座位总数
            ret[i] = (i - 1 >= 0 ? ret[i - 1] : 0) + arr[i]; // 当前航班的预订座位数等于前一个航班的预订座位数加上当前差分数组的值
            // BUG(cout << ret[i] << endl;); // 如果启用DEBUG模式,输出当前航班的预订座位数
            BUG(cout << arr[i] << endl;); // 如果启用DEBUG模式,输出当前差分数组的值
        }
    }

    vector<int> corpFlightBookings(vector<vector<int>>& _bookings, int _n) {
        ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 优化输入输出
        bookings = _bookings, n = _n; // 将输入参数赋值给类成员变量
        solve(); // 调用solve函数处理预订记录
        return ret; // 返回结果数组
    }
};

;

P4231 三步必杀

三步必杀

题目背景

(三)旧都

离开狭窄的洞穴,眼前豁然开朗。

天空飘着不寻常的雪花。

一反之前的幽闭,现在面对的,是繁华的街市,可以听见酒碗碰撞的声音。

这是由被人们厌恶的鬼族和其他妖怪们组成的小社会,一片其乐融融的景象。

诶,不远处突然出现了一些密密麻麻的小点,好像大颗粒扬尘一样。

离得近了点,终于看清楚了。

长着角的鬼们聚在一起,围观着另一只鬼的表演。

那”扬尘”,其实都是弹幕。

勇仪的招数之一,三步之内,所到之处弹幕云集,几乎没有生存可能。

为了强化这一技能,勇仪将对着一排柱子进行攻击。

旧地狱的柱子虽然无比坚固,但保险起见还是先要了解一下这样一套攻击对柱子有多少损伤,顺带也能检验练习的效果。

勇仪决定和其它鬼们商量商量...

“我知道妖怪之山的河童一族有一种叫做计算机的神奇道具,说不定可以借来用用”,萃香说道。

于是旧地狱的鬼族就决定请河城荷取来帮忙了。

“要记录【所有柱子的损伤程度】吗”,荷取问道。

经过进一步的询问,荷取发现他们仅仅需要【所有攻击都完成后】柱子的损伤程度。

任务了解地差不多了,荷取将其中的重要部分提取了出来,记录在了她的工作笔记本上:

(记录的内容见题目描述)

那么实验就这样开始了。

在惊天动地的碰撞声中,勇仪每完成一轮攻击,荷取都忠实地记录下对每根柱子产生的伤害。而此时勇仪就在旁边等待着记录完成,然后再进行下一轮的攻击。

地面上,天色渐晚。

“不想在这里留到深夜啊,不然就回不了家了”,荷取这样想着,手中依然在重复地向计算机中输入新产生的信息。

“真的必须一次一次地记录下每轮攻击对每个柱子产生的伤害吗?有没有更高效的方法?”这一念头在荷取的心中闪过...

(后续剧情在题解中,接下来请看T3)

题目描述

问题摘要:

$$$$个柱子排成一排,一开始每个柱子损伤度为0。

接下来勇仪会进行$M$次攻击,每次攻击可以用4个参数$l$,$r$,$s$,$e$来描述:

表示这次攻击作用范围为第$l$个到第$r$个之间所有的柱子(包含$l$,$r$),对第一个柱子的伤害为$s$,对最后一个柱子的伤害为$e$。

攻击产生的伤害值是一个等差数列。若$l=1$,$r=5$,$s=2$,$e=10$,则对第1~5个柱子分别产生2,4,6,8,10的伤害。

鬼族们需要的是所有攻击完成之后每个柱子的损伤度。

输入格式

第一行2个整数$N$,$M$,用空格隔开,下同。

接下来$M$行,每行4个整数$l$,$r$,$s$,$e$,含义见题目描述。

数据保证对每个柱子产生的每次伤害值都是整数。

输出格式

由于输出数据可能过大无法全部输出,为了确保你真的能维护所有柱子的损伤度,只要输出它们的异或和与最大值即可。

(异或和就是所有数字按位异或起来的值)

(异或运算符在c++里为^)

样例 #1

样例输入 #1

5 2 1 5 2 10 2 4 1 1

样例输出 #1

3 10

样例 #2

样例输入 #2

6 2 1 5 2 10 2 4 1 1

样例输出 #2

3 10

提示

样例解释:

样例1:

第一次攻击产生的伤害:2 4 6 8 10

第二次攻击产生的伤害:0 1 1 1 0

所有攻击结束后每个柱子的损伤程度:2 5 7 9 10。

输出异或和与最大值,就是3 10。

样例2:

没有打到第六根柱子,答案不变

数据范围:

本题满分为100分,下面是4个子任务。(x/y)表示(得分/测试点数量)

妖精级(18/3):$1\leqslant n$,$m\leqslant1000$。这种工作即使像妖精一样玩玩闹闹也能完成吧?

河童级(10/1):$s=e$,这可以代替我工作吗?

天狗级(20/4):$1\leqslant n\leqslant10^5$,$1\leqslant m\leqslant10^5$。小打小闹不再可行了呢。

鬼神级(52/2):没有特殊限制。要真正开始思考了。

以上四部分数据不相交。

对于全部的数据:$1\leqslant n\leqslant10^7$,$1\leqslant m\leqslant3\times 10^5$,$1\leqslant l<r\leqslant n$.

所有输入输出数据以及柱子受损伤程度始终在$[0,9\times 10^{18}]$范围内。

提示:

由于种种原因,时间限制可能会比较紧,C++选手请不要使用cin读入数据。

by orangebird

1.

代码

 
#include<bits/stdc++.h> // 包含所有C++标准库头文件
using namespace std;

#define int long long // 定义int为long long类型,方便处理大整数
#define endl '\n' // 定义endl为换行符,简化代码
#define FAST() ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); // 优化输入输出,加快执行速度

#define p pair<int,int> // 定义p为pair<int, int>类型,简化代码
#define ff first // 定义ff为first,简化pair的使用
#define ss second // 定义ss为second,简化pair的使用

#define ltu(i,a,b) for(int i=a;i<=b;i++) // 定义从a到b的递增循环
#define utl(i,a,b) for(int i=a;i>=b;i--) // 定义从a到b的递减循环
#define tests() int t;cin>>t;while(t--) // 定义多组测试数据的输入方式

int n, m; // n为柱子的数量,m为攻击次数
struct node { // 定义结构体node用于存储攻击信息
    int l, r, s, e; // l为起始柱子,r为结束柱子,s为起始伤害,e为结束伤害
};
vector<node> readd; // 存储所有攻击的向量
vector<int> arr; // 差分数组,用于记录伤害
int ret_max; // 记录所有柱子中最大损伤度
int ret; // 记录所有柱子的损伤度的异或和

void solve() {
    ret = 0; // 初始化异或和为0
    ret_max = LLONG_MIN; // 初始化最大损伤度为最小的long long值
    arr.assign(n + 5, 0); // 初始化差分数组,大小为n+5,初始值为0
    for (auto& x : readd) { // 遍历每一个攻击记录
        int d = (x.e - x.s) / (x.r - x.l); // 计算等差数列的公差
        arr[x.l] += x.s; // 在起始位置增加初始伤害
        arr[x.l + 1] += d - x.s; // 在起始位置的下一位增加公差减去初始伤害
        arr[x.r + 1] += -d - x.e; // 在结束位置的下一位减少公差和结束伤害
        arr[x.r + 2] += x.e; // 在结束位置的下两位增加结束伤害
    }

    ltu(i, 1, n) { // 第一次构建前缀和
        arr[i] = (i - 1 >= 0 ? arr[i - 1] : 0) + arr[i]; // 前缀和累加
    }
    ltu(i, 1, n) { // 第二次构建前缀和
        arr[i] = (i - 1 >= 0 ? arr[i - 1] : 0) + arr[i]; // 前缀和累加
        ret_max = max(ret_max, arr[i]); // 更新最大损伤度
        ret ^= arr[i]; // 计算损伤度的异或和
    }
    cout << ret << " " << ret_max << endl; // 输出异或和和最大损伤度
}

signed main() {
    FAST(); // 优化输入输出
    cin >> n >> m; // 输入柱子数量和攻击次数
    readd.clear(); // 清空攻击记录向量
    ltu(i, 1, m) { // 输入每一条攻击记录
        node temp = node(); // 定义临时变量存储攻击信息
        cin >> temp.l >> temp.r >> temp.s >> temp.e; // 输入攻击信息
        readd.push_back(temp); // 将攻击信息加入向量
    }
    solve(); // 调用solve函数计算结果
}

P5026 Lycanthropy

Lycanthropy

题目背景

小正方形亲眼看见了自己昔日的朋友被卷进了黑暗的深渊,然而它无力阻止……

现在它的朋友已经向它发起了攻击,因此小正方形不得不抵抗。

题目描述

我们把山顶上的湖泊看作一条长度为 $$$$ 的直线,一开始水深都在水平线上,我们视作此时的水深为 '0'

接下来,在一瞬间,小正方形的"朋友"们跳起并扎入水中,导致在入水点的水降低而远离入水点的水升高,注意两个 "朋友" 可能在同一地点入水。

小正方形的每个朋友有一个体积数值 $v$,当体积为 $$$$ 的一个朋友跳入水中,我们设入水点为 $i$,将会导致 $$i - v + $$ 到 $$$$ 的水位依次降低 $$1,2,\cdots,$$

同样地,第 $$$$ 到 $$i + v - $$ 的水位会依次降低 $v,v - 1,\cdots,1$.

相对应地,$i - v$ 的水位不变, $$i - v - $$ 到 $$i - 2 * $$ 水位依次增加 $1,2,\cdots,v$, $$i - 2 * $$ 到 $$i - 3 * v + $$ 水位依次增加 $$v,v - 1,\cdots,$$

同样,$i + v$ 水位不变,$i + v + 1$ 到 $$i + 2 * $$ 水位增加 $1,2,\cdots,v$,$i + 2 * v$ 到 $$i + 3 * v - $$ 水位依次增加 $$v,v - 1,\cdots,$$

现在小正方形想要穿过这个湖,他想要知道在这 $$$$ 个"朋友"跳入水中后湖上每个节点的水位,你能帮帮它吗?

输入格式

第一行为两个整数 $n$,$m$,表示"朋友"的数目与湖泊的宽度。

接下来 $$$$ 行,一行两个整数 $v,x$,表示第 $$i + $$ 个朋友的体积与入水点。

输出格式

一行 $$$$ 个整数,第 $$$$ 个整数表示 $$$$ 号位的水深。

样例 #1

样例输入 #1

1 10 1 5

样例输出 #1

0 0 1 0 -1 0 1 0 0 0

样例 #2

样例输入 #2

2 10 2 6 3 1

样例输出 #2

-2 0 0 0 0 0 2 2 2 2

提示

对于 $$30\$$ 的数据,$$n <= 50,m <= 50$$

对于 $$70\$$ 的数据,$$n <= 10^5,m <= 10^$$

对于 $$100\$$ 的数据,$$n <= 10^6,m <= 10^6,1 <= v <= 10000,1 <= x <= $$

1.

sett(i-3*v+1,i-2*v,1,v);

sett(i-2*v+1,i,v-1,-v);

sett(i+1,i+2*v,-v+1,v);

sett(i+2*v+1,i+3*v-1,v-1,1);

sett函数有四个参数,sett(l,r,s,e)

表示从区间l~r依次加上等差数列,s到e.

2.

MAXN是湖最大的长度,用于做偏移量.

当我们处理落点x,附近的水高低的时候,处理边界情况很麻烦。

因为可能出现负数,或者可能越界.

因此我们可以开辟大一点的空间,并做一个较大的偏移量,此时不会出现负数也不会越上界

对于每一个区间l~r,做一个偏移量MAXN

"I+=MAXN,r+=MAXN"

3.

build函数计算前缀和.

arr数组开辟的空间大小是2*MAXN+m

原本需要开辟的空间是m左右,然后在前面和后面多开辟一个MAXN

这样就不用考虑边界情况了.

我们需要用到的是1~m偏移一个MAXN的区间位置.

代码

 
#include <bits/stdc++.h> // 包含所有C++标准库头文件
using namespace std;

#define DEBUG
#ifdef DEBUG
#define BUG(code) do{code}while(0)
#else
#define BUG(code) do{}while(0)
#endif // DEBUG

#define int long long // 定义int为long long类型,方便处理大整数
#define endl '\n' // 定义endl为换行符,简化代码
#define FAST() ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) // 优化输入输出,加快执行速度

#define ltu(i,a,b) for(int i=a;i<=b;i++) // 定义从a到b的递增循环
#define utl(i,a,b) for(int i=a;i>=b;i--) // 定义从a到b的递减循环
#define tests() int t;cin>>t;while(t--) // 定义多组测试数据的输入方式

const int MAXN = 1e6 + 10; // 定义常量MAXN,用于数组的大小
int n, m; // n为朋友的数量,m为湖泊的宽度
struct node { // 定义结构体node用于存储朋友的体积和入水点
    int v, x; // v为体积,x为入水点
};
vector<node> readd; // 存储所有朋友信息的向量
vector<int> arr; // 差分数组,用于记录水位变化

void sett(int l, int r, int s, int e) {
    l += MAXN; // 调整左边界,避免负索引
    r += MAXN; // 调整右边界,避免负索引

    int d = (r != l) ? (e - s) / (r - l) : 0; // 计算等差数列的公差
    arr[l] += s; // 在左边界加上初始值s
    arr[l + 1] += d - s; // 在左边界的下一位置加上d-s
    arr[r + 1] += -d - e; // 在右边界的下一位置减去d和e
    arr[r + 2] += e; // 在右边界的下两位置加上e
}

void build() {
    ltu(i, 1, 2 * MAXN + m - 1) { // 构建前缀和
        arr[i] += arr[i - 1]; // 前缀和累加
    }
}

void solve() {
    arr.assign(2 * MAXN + m, 0); // 初始化差分数组,大小为2*MAXN+m,初始值为0

    for (auto& xx : readd) { // 遍历每一个朋友的信息
        int i = xx.x; // 读取入水点
        int v = xx.v; // 读取体积
        sett(i - 3 * v + 1, i - 2 * v, 1, v); // 处理左侧影响
        sett(i - 2 * v + 1, i, v - 1, -v); // 处理左侧与中间的影响
        sett(i + 1, i + 2 * v, -v + 1, v); // 处理中间与右侧的影响
        sett(i + 2 * v + 1, i + 3 * v - 1, v - 1, 1); // 处理右侧影响
    }

    build(); // 构建前缀和
    build(); // 第二次构建前缀和

    ltu(i, MAXN + 1, MAXN + m) { // 输出每个位置的水位
        cout << arr[i] << " "; // 输出水位
    }
    cout << endl; // 输出换行
}

signed main() {
    FAST(); // 优化输入输出
    cin >> n >> m; // 输入朋友的数量和湖泊的宽度
    readd.clear(); // 清空朋友信息向量
    ltu(i, 1, n) { // 输入每一个朋友的信息
        node temp; // 定义临时变量存储朋友信息
        cin >> temp.v >> temp.x; // 输入朋友的体积和入水点
        readd.push_back(temp); // 将朋友信息加入向量
    }
    solve(); // 调用solve函数计算结果
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值