UVa 12436 Rip Van Winkle‘s Code

题目描述

Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 厌倦了除编程之外的一切。有一天他遇到了一个问题,需要对数组 data[]\texttt{data[]}data[] 执行三种更新操作(ABC)和一种查询操作 S。初始时,数组的所有元素都为 000。虽然 Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 要睡 202020 年,他的代码也非常慢,但你需要以高效的方式执行相同的更新操作,并输出查询操作 S 的结果。

给定的慢速代码如下:

long long data[250001];
void A(int st, int nd) {
    for(int i = st; i <= nd; i++) data[i] = data[i] + (i - st + 1);
}
void B(int st, int nd) {
    for(int i = st; i <= nd; i++) data[i] = data[i] + (nd - i + 1);
}
void C(int st, int nd, int x) {
    for(int i = st; i <= nd; i++) data[i] = x;
}
long long S(int st, int nd) {
    long long res = 0;
    for(int i = st; i <= nd; i++) res += data[i];
    return res;
}

输入格式

第一行包含整数 TTT≤4×105\leq 4 \times 10^54×105),表示操作数量。接下来的 TTT 行,每行以一个字符开头(A\texttt{A}AB\texttt{B}BC\texttt{C}CS\texttt{S}S),表示操作类型。字符 A\texttt{A}AB\texttt{B}BS\texttt{S}S 后跟两个整数 stststndndnd;字符 C\texttt{C}C 后跟三个整数 stststndndndxxx。假设 1≤st≤nd≤2500001 \leq st \leq nd \leq 2500001stnd250000−105≤x≤105-10^5 \leq x \leq 10^5105x105

输出格式

对于每个以字符 S\texttt{S}S 开头的行,输出 S(st,nd)S(st, nd)S(st,nd) 的结果。

题目分析

直接模拟的问题

如果直接按照题目给出的代码实现,时间复杂度会非常高:

  • 数组大小:250,000250,000250,000
  • 操作次数:最多 400,000400,000400,000
  • 每次操作可能覆盖整个数组

最坏情况下,总时间复杂度为 O(T×N)O(T \times N)O(T×N),即 400,000×250,000=1011400,000 \times 250,000 = 10^{11}400,000×250,000=1011 次操作,这显然会超时。

操作分析

我们需要处理四种操作:

  1. 操作 A:在区间 [st,nd][st, nd][st,nd] 上,对每个位置 iii 加上 (i−st+1)(i - st + 1)(ist+1)

    • 这是一个从 111 开始递增的等差数列
  2. 操作 B:在区间 [st,nd][st, nd][st,nd] 上,对每个位置 iii 加上 (nd−i+1)(nd - i + 1)(ndi+1)

    • 这是一个从大到小递减的等差数列
  3. 操作 C:在区间 [st,nd][st, nd][st,nd] 上,将所有元素设置为 xxx

    • 这是一个区间覆盖操作
  4. 操作 S:查询区间 [st,nd][st, nd][st,nd] 的和

数学建模

对于等差数列操作,我们可以用线性函数来表示:

  • 操作 A:对于位置 iii,加上 i×1+(−st+1)i \times 1 + (-st + 1)i×1+(st+1)

    • k=1k = 1k=1, b=−st+1b = -st + 1b=st+1
  • 操作 B:对于位置 iii,加上 i×(−1)+(nd+1)i \times (-1) + (nd + 1)i×(1)+(nd+1)

    • k=−1k = -1k=1, b=nd+1b = nd + 1b=nd+1

等差数列在区间 [l,r][l, r][l,r] 上的和可以用数学公式直接计算:

∑i=lr(k⋅i+b)=k⋅(l+r)⋅(r−l+1)2+b⋅(r−l+1)\sum_{i=l}^{r} (k \cdot i + b) = k \cdot \frac{(l + r) \cdot (r - l + 1)}{2} + b \cdot (r - l + 1)i=lr(ki+b)=k2(l+r)(rl+1)+b(rl+1)

这个公式让我们可以在 O(1)O(1)O(1) 时间内计算任意区间上等差数列的和。

解题思路

数据结构选择

由于需要高效处理区间更新和区间查询,我们选择线段树作为基础数据结构。每个线段树节点需要维护:

  • sum\texttt{sum}sum:区间和
  • cover\texttt{cover}cover:覆盖标记(特殊值表示未覆盖)
  • kkk, bbb:线性标记,表示需要加上的函数 k⋅i+bk \cdot i + bki+b
  • len\texttt{len}len:区间长度

懒标记处理

我们需要处理两种类型的懒标记:

  1. 覆盖标记:优先级最高,会清除所有线性标记
  2. 线性标记:可以累积,表示需要叠加的等差数列

pushDown\texttt{pushDown}pushDown 操作时:

  • 先处理覆盖标记(如果有)
  • 再处理线性标记(如果有)

时间复杂度

使用线段树后,每个操作的时间复杂度为 O(log⁡N)O(\log N)O(logN),总时间复杂度为 O(Tlog⁡N)O(T \log N)O(TlogN),在 T=4×105T = 4 \times 10^5T=4×105N=2.5×105N = 2.5 \times 10^5N=2.5×105 的情况下是可行的。

代码实现

// Rip Van Winkle's Code
// UVa ID: 12436
// Verdict: Accepted
// Submission Date: 2025-11-30
// UVa Run Time: 0.200s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 250005;

struct Node {
    long long sum;          // 区间和
    long long cover;        // 覆盖标记,INF表示未覆盖
    long long k, b;         // 线性标记:k*i + b
    int len;                // 区间长度
} tree[MAXN * 4];

const long long INF = 1e18;

void build(int node, int l, int r) {
    tree[node] = {0, INF, 0, 0, r - l + 1};
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(node << 1, l, mid);
    build(node << 1 | 1, mid + 1, r);
}

// 计算线性函数在区间[l, r]上的和
inline long long calcLinearSum(int l, int r, long long k, long long b) {
    return k * (l + r) * (r - l + 1) / 2 + b * (r - l + 1);
}

// 应用覆盖标记
void applyCover(int node, int l, int r, long long val) {
    tree[node].sum = (r - l + 1) * val;
    tree[node].cover = val;
    tree[node].k = tree[node].b = 0;
}

// 应用线性标记
void applyLinear(int node, int l, int r, long long k, long long b) {
    tree[node].sum += calcLinearSum(l, r, k, b);
    tree[node].k += k;
    tree[node].b += b;
}

// 下推懒标记
void pushDown(int node, int l, int r) {
    if (l == r) return;
    
    int mid = (l + r) >> 1;
    int left = node << 1, right = node << 1 | 1;
    
    // 先处理覆盖标记(优先级最高)
    if (tree[node].cover != INF) {
        applyCover(left, l, mid, tree[node].cover);
        applyCover(right, mid + 1, r, tree[node].cover);
        tree[node].cover = INF;
    }
    
    // 再处理线性标记
    if (tree[node].k != 0 || tree[node].b != 0) {
        applyLinear(left, l, mid, tree[node].k, tree[node].b);
        applyLinear(right, mid + 1, r, tree[node].k, tree[node].b);
        tree[node].k = tree[node].b = 0;
    }
}

// 区间覆盖更新
void updateCover(int node, int l, int r, int ql, int qr, long long val) {
    if (ql <= l && r <= qr) {
        applyCover(node, l, r, val);
        return;
    }
    pushDown(node, l, r);
    int mid = (l + r) >> 1;
    if (ql <= mid) updateCover(node << 1, l, mid, ql, qr, val);
    if (qr > mid) updateCover(node << 1 | 1, mid + 1, r, ql, qr, val);
    tree[node].sum = tree[node << 1].sum + tree[node << 1 | 1].sum;
}

// 区间线性更新
void updateLinear(int node, int l, int r, int ql, int qr, long long k, long long b) {
    if (ql <= l && r <= qr) {
        applyLinear(node, l, r, k, b);
        return;
    }
    pushDown(node, l, r);
    int mid = (l + r) >> 1;
    if (ql <= mid) updateLinear(node << 1, l, mid, ql, qr, k, b);
    if (qr > mid) updateLinear(node << 1 | 1, mid + 1, r, ql, qr, k, b);
    tree[node].sum = tree[node << 1].sum + tree[node << 1 | 1].sum;
}

// 区间查询
long long query(int node, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) return tree[node].sum;
    pushDown(node, l, r);
    int mid = (l + r) >> 1;
    long long res = 0;
    if (ql <= mid) res += query(node << 1, l, mid, ql, qr);
    if (qr > mid) res += query(node << 1 | 1, mid + 1, r, ql, qr);
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;
    cin >> t;
    build(1, 1, 250000);
    
    while (t--) {
        char op;
        cin >> op;
        if (op == 'A') {
            int st, nd;
            cin >> st >> nd;
            updateLinear(1, 1, 250000, st, nd, 1, -st + 1);
        } else if (op == 'B') {
            int st, nd;
            cin >> st >> nd;
            updateLinear(1, 1, 250000, st, nd, -1, nd + 1);
        } else if (op == 'C') {
            int st, nd, x;
            cin >> st >> nd >> x;
            updateCover(1, 1, 250000, st, nd, x);
        } else {
            int st, nd;
            cin >> st >> nd;
            cout << query(1, 1, 250000, st, nd) << '\n';
        }
    }
    
    return 0;
}

关键优化点

  1. 数学公式计算:使用等差数列求和公式在 O(1)O(1)O(1) 时间内计算区间和,避免了循环计算
  2. 懒标记设计:合理设计覆盖标记和线性标记的优先级和处理顺序
  3. 代码优化:使用内联函数、减少不必要的函数调用等技巧

总结

本题的关键在于将区间上的等差数列操作转化为线性函数的叠加,并利用线段树高效处理。通过数学建模和合理的数据结构设计,我们将原本 O(T×N)O(T \times N)O(T×N) 的时间复杂度优化到了 O(Tlog⁡N)O(T \log N)O(TlogN),成功解决了 Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 的慢速代码问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值