维护序列c++

今天我们来讲一讲NOIP里的真题吧!

目录

一。题目 

二。分析

三。代码


一。题目 

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
有长为 N 的数列,不妨设为 1,a2,...,an。
有如下三种操作形式:
1.把数列中的一段数全部乘一个值
2.把数列中的一段数全部加一个值;
3.询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值
输入格式
第一行两个整数 N 和 P
第二行含有 N 个非负整数,从左到右依次为 a1,a2,.·,an;
第三行有一个整数 M,表示操作总数
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1: 1t gc,表示把所有满足t <i< g的ai 改为 aix c;。操作2: 2tgc,表示把所有满足t < i < g的a;为 ai + c;·操作3: 3 t g ,询问所有满足t < i g 的a的和模 P 的值
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。输出格式
对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
数据范围
1 ≤ N, M < 105
1<t<g< N,0 < c,ai < 109
1 < P < 109
输入样例:
7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7
输出样例:
2
35
8
样例解释
初始时数列为{1,2,3,4,5,6,7;
经过第 1次操作后,数列为 1,10,15,20,25,6,7\;
对第 2 次操作,和为 10 + 15 + 20 = 45,模43 的结果是 2;
经过第 3 次操作后,数列为[1,10,24,29,34,15.161:
对第 4 次操作,和为 1 + 10 - 24 = 35,模 43 的结果是 35:
对第 5 次操作,和为 29 + 34 + 15 + 16 94,模 43 的结果是 8。

二。分析

今天的这一道题的题目确实有点长,咱们来像语文一样慢慢品析吧!

这道题的修改操作有两种,在区间[l,r]上都加d,在区间[l,r]上都乘以d。那么对于修改操作来说,我们是先加d后乘d,还是先乘d后加d呢?

假设我们是先加d后乘d。设add为加法的懒标记,mul为乘法的懒标记。如果是先加再乘,即有这种形式(s+add)*mul,这种形式对于乘上一个数d是很好维护的,即(s+add)× m u l × d \times mul\times d×mul×d,那么我们只需要让m u l = m u l × d mul=mul\times dmul=mul×d即可,但是对于加上d就不是那么好维护了,即( s + a d d ) × m u l + d (s+add)\times mul+d(s+add)×mul+d,如果m u l = k d mul=kdmul=kd,那么( s + a d d ) × m u l + m u l k = ( s + a d d + 1 k ) × m u l (s+add)\times mul+\dfrac {mul}{k}=(s+add+\dfrac {1}{k})\times mul(s+add)×mul+ kmul=(s+add+ k1​)×mul,因为此时出现了除法,比较容易丢失精度,大体上还是能够实现( s + a d d ) × m u l (s+add)\times mul(s+add)×mul这种形式,但是如果mul不能除以d,那么就不能写成( s + a d d ) × m u l (s+add)\times mul(s+add)×mul这种形式了,因此就很难维护了。

假设我们是先乘d后加d。设add为加法的懒标记,mul为乘法的懒标记。如果是先乘再加,即有这种形式s × m u l + a d d s\times mul+adds×mul+add。这种形式对于乘法来说,即( s × m u l + a d d ) × d = s × m u l × d + a d d × d (s\times mul+add)\times d=s\times mul\times d+add\times d(s×mul+add)×d=s×mul×d+add×d,那么我们只需让m u l = m u l × d mul=mul\times dmul=mul×d和a d d = a d d × d add=add\times dadd=add×d即可。对于加法来说,即s × m u l + a d d + d s\times mul+add+ds×mul+add+d,那么我们只需让a d d = a d d + d add=add+dadd=add+d即可。由此我们发现,对于先乘后加,是非常容易维护的。因此,我们选择先乘后加。

这里对于这两种操作,我们已经选择了先乘后加,但是我们都需要对乘法和加法写关于懒标签的函数。其实,这里有个优化:

我们可以认为有种最初形式:x × m u l + a d d x\times mul+addx×mul+add,假设来了乘法,则让add=0,则会得到x × m u l x\times mulx×mul的形式;假设来了加法,则让mul=1,则会得到x + a d d x+addx+add的形式。

那么我们认为的这种最初形式会对加法和乘法的懒标记有什么影响呢?

因为我们是先乘后加,所以有 最初形式*c+d。即会有( x × m u l + a d d ) × c + d = x × m u l × c + a d d × c + d (x\times mul+add)\times c+d=x\times mul\times c+add\times c+d(x×mul+add)×c+d=x×mul×c+add×c+d,因此,我们需要让m u l = m u l × c mul=mul\times cmul=mul×c和a d d = a d d × c + d add=add\times c+dadd=add×c+d即可。

问题1:为什么是 t.sum = ((LL)t.sum * mul + (LL)(t.r - t.l + 1) * add) % p;而不是 t.sum = ((LL)t.sum * mul(t.r - t.l + 1) + (LL)(t.r - t.l + 1) * add) % p;呢?*

因为我们选择了先乘后加,即采用了s × m u l + a d d s\times mul+adds×mul+add的形式。比如设y = a x + b y=ax+by=ax+b,不放设a = 2 , b = 5 a=2,b=5a=2,b=5,那么y = 2 x + 5 y=2x+5y=2x+5。

三。代码

#include <iostream>
using namespace std;
const int N = 2000;
int num[N] = {0};
int main(){
    int p, q, id, index, temp;
    cin>>p;
    for(int i = 0; i<p; i++)
        cin>>num[i];
    cin>>q;
    for(int i = 0; i<q; i++){
        cin>>id;
        if(id == 1){
            cin>>index;
            cout<<num[index-1]<<endl;
        }
        if(id == 2){
            cin>>index>>temp;
            int pos = index-1;
            for(int j = p ; j > pos ; j--)
                num[j] = num[j-1];
            num[pos] = temp;
            p++;
        }
        if(id == 3){
            cin>>index;
            for(int j = index - 1; j < p; j++)
                num[j] = num[j+1];
            p--;
        }
    }
//  for(int i = 0; i<p; i++){
//      cout<<num[i]<<" ";
//  }
    return 0;
}
在LeetCode(力扣)上,有一个经典的算法题目叫做“最长连续序列”(Longest Continuous Increasing Subsequence),通常用C++或其他编程语言进行解答。这个题目的目的是找到一个给定整数数组中的最长递增子序列。递增子序列是数组中的一段连续元素,它们按顺序严格增大。 这里是一个简单的C++解决方案思路: ```cpp #include <vector> using namespace std; class Solution { public: int longestContinuousIncreasingSubsequence(vector<int>& nums) { if (nums.empty()) return 0; // 避免空数组的情况 int n = nums.size(); vector<int> dp(n, 1); // dp[i] 表示以nums[i]结尾的最长递增子序列长度 int max_len = 1; // 初始化最长递增子序列长度为1 for (int i = 1; i < n; ++i) { // 遍历数组,从第二个元素开始 if (nums[i] > nums[i-1]) { // 如果当前元素比前一个大 dp[i] = dp[i-1] + 1; // 更新dp值,考虑加入当前元素后的增长长度 max_len = max(max_len, dp[i]); // 检查是否更新了最长子序列长度 } } return max_len; // 返回最长连续递增子序列的长度 } }; ``` 在这个代码中,我们使用了一个动态规划(Dynamic Programming)的方法,维护一个数组`dp`来存储每个位置以该位置元素结尾的最大递增子序列长度。遍历过程中,如果遇到当前元素大于前一个元素,则说明可以形成一个新的递增子序列,所以将`dp[i]`设置为`dp[i-1]+1`,并更新全局的最长子序列长度。 如果你想要深入了解这个问题,可以问: 1. 这个问题的时间复杂度是多少? 2. 动态规划是如何助解决这个问题的? 3. 如何优化这个算法,使其空间复杂度更低?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值