[洛谷P4231] 三步必杀

二阶差分好题

关于差分:对于区间修改通常用到差分这一方法。

方法是 建立一个差分数组\(a\),也就是原数列后一位减前一位的值\((a[i] = a[i] - a[i - 1])\)

当某一个区间加上一个值\(w\)之后,该区间的左端点就会比左端点减一的值多\(w\),而右端点加一就会比右端点的值少\(w\)

所以对于修改的区间\((l,r)\)\(a[l] += w, a[r + 1] -= w\); 时间复杂度为\(O(1)\),比数据结构优。

查询值的时候,只需要将差分数组做一遍前缀和即可。
原理是 当\(a[l]\)加上\(w\)时,做前缀和的时候从l一直到数列最后都会受到加\(w\)的影响,
因为会影响区间外的值,所以在右端点加\(1\)处减去\(w\),来消除从右端点加一到数列最后的影响。

时间复杂度\(O(n)\),比线段树等数据结构慢很多
所以一般当只查询最后结果时,用差分这一方法比较优。


关于本题:可以很自然的想到差分这一方法。
但是区间修改时不是加上或减去同一个值,而是一个等差数列

怎么办呢?

考虑对于一个等差数列,它的差分数组很显然都是同一个值
而对于一个区间,每个点要加的值又是一个等差数列
所以需要知道 等差数列的差分数组 来统计 区间每个点要加的值 的 差分数组

怎样得到等差数列的差分数组\(sumcha\)呢?(为了方便理解,数组名称使用代码中的变量)

考虑再进行一次差分,统计\(sumcha\)的差分数组\(chacha\),很显然只需要修改区间左右端点即可
这是因为 \(sumcha\)每个值都相同,所以只需要在\(chacha\)开头加上公差,结尾加一处减去公差,做前缀和就可以得到\(sumcha\)

所以对这个数组做\(1-n\)的前缀和可以得到\(sumcha\),这个就是整个数列的差分数组
然后再对整个序列的差分数组做前缀和,就可以得到每个点修改的值。

例如:对于\(0\ 0\ 0\ 0\ 0\)这个序列,执行\((1\ 5\ 2\ 10)\)这个操作
\(chacha[1] += 2, chacha[6] -= 2\)
做前缀和可以得到:\(2\ 2\ 2\ 2\ 2\)
这很显然就是\(2\ 4\ 6\ 8\ 10\)的差分数组
再对它做前缀和,就可以得到:\(2\ 4\ 6\ 8\ 10\)
也就是每个点要加的值

注意:

  • 等差数列的开头不一定是等差数列的公差,比如:\(4\ 6\ 8\ 10\)
    所以在统计等差数列的差分数组时不能仅仅将差分数组的左右断点修改,
    还要在左端点加上开头的数与公差的差,在右端点加一处消除影响。
  • 因为做了多遍差分,所以记得多次消除影响,比如说\(sumcha\)也是一个差分数组,对它也需要进行消除影响
  • 记得开\(long\ long\)

代码:

#include <cstdio>
#include <cctype>

typedef long long ll;
const int _ = 10000001;
ll sum_ans[_], cha_cha[_], sum_cha[_];

inline ll max(ll a, ll b) { return a > b ? a : b; }
inline ll read() {
    ll s = 1, w = 0; char ch = getchar();
    for(; ! isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = w * 10 + ch - '0';
    return s * w;
}

int main() {
    int n = read(), m = read(), l, r; ll s, e, cha;
    while (m --) {
      l = read(), r = read(); s = read(), e = read(), cha;
      cha = (e - s) / (r - l);//计算公差 
      cha_cha[l] += cha, cha_cha[r + 1] -= cha; //等差数列的差分的差分= = 
      sum_ans[l] += s - cha, sum_ans[r + 1] -= s - cha; //当等差数列开头不为公差时 
      sum_ans[r + 1] -= (r - l + 1) * cha;//等差数列差分 
    }
    ll Max = 0, ans = 0;
    for (register int i = 1; i <= n; i ++) { 
      sum_cha[i] = sum_cha[i - 1] + cha_cha[i]; //前缀和求等差数列的差分数组 
      sum_ans[i] += sum_ans[i - 1] + sum_cha[i];//前缀和求每个点要加的值 
      ans ^= sum_ans[i], Max = max(Max, sum_ans[i]);
    } 
    printf("%lld %lld", ans, Max);
    return 0; 
}

猜你喜欢

转载自www.cnblogs.com/clickidea/p/11715526.html