当前位置:首页 > 工具 > 正文内容

区间元素替换与受限网格路径计数的算法解析

访客 工具 2026年9月8日 1

问题一:区间特定值替换

问题描述

给定一个长度为 $n$ 的数列 $a$,其中元素值域较小($a_i \le 100$)。需要执行 $q$ 次操作,每次操作给定区间 $[l, r]$ 以及两个值 $x$ 和 $y$,要求将区间内所有等于 $x$ 的元素替换为 $y$。最终输出操作完成后的数列。

算法思路

由于数列元素的值域极小,直接采用线段树维护会面临较大的常数开销。此时,分块算法是一个更优的选择。

我们将数列划分为若干个大小约为 $\sqrt{n}$ 的块。对于每个块,维护一个映射数组 `tag`,其中 `tag[v]` 表示该块内原本值为 $v$ 的元素当前实际对应的值。

  • 整块修改:当操作区间完全覆盖某个块时,只需遍历该块的 `tag` 数组(长度最大为 100),将所有等于 $x$ 的映射值修改为 $y$。时间复杂度为 $O(V)$,其中 $V$ 为值域大小。
  • 零散块修改:当操作区间仅覆盖块的某一部分时,首先将该块的 `tag` 映射下传到实际数组中,并重置 `tag` 数组。然后暴力遍历区间内的元素进行替换。时间复杂度为 $O(\sqrt{n})$。

通过这种分块与值域映射结合的方式,可以高效地处理区间替换操作。

代码实现

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

const int MAXN = 200005;
const int MAXV = 105;
const int BLOCK_SIZE = 450;

int n, q;
int arr[MAXN];
int block_tag[MAXN / BLOCK_SIZE + 5][MAXV];

inline int get_block(int idx) {
    return idx / BLOCK_SIZE;
}

inline void push_down(int b) {
    int start = b * BLOCK_SIZE;
    int end = min(n, start + BLOCK_SIZE - 1);
    for (int i = start; i <= end; ++i) {
        arr[i] = block_tag[b][arr[i]];
    }
    for (int v = 1; v < MAXV; ++v) {
        block_tag[b][v] = v;
    }
}

inline void update_range(int l, int r, int x, int y) {
    if (x == y) return;
    int bl = get_block(l);
    int br = get_block(r);

    if (bl == br) {
        push_down(bl);
        for (int i = l; i <= r; ++i) {
            if (arr[i] == x) arr[i] = y;
        }
    } else {
        push_down(bl);
        int end_bl = (bl + 1) * BLOCK_SIZE - 1;
        for (int i = l; i <= end_bl; ++i) {
            if (arr[i] == x) arr[i] = y;
        }

        for (int b = bl + 1; b < br; ++b) {
            for (int v = 1; v < MAXV; ++v) {
                if (block_tag[b][v] == x) {
                    block_tag[b][v] = y;
                }
            }
        }

        push_down(br);
        int start_br = br * BLOCK_SIZE;
        for (int i = start_br; i <= r; ++i) {
            if (arr[i] == x) arr[i] = y;
        }
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> arr[i];
    }

    int num_blocks = get_block(n - 1) + 1;
    for (int b = 0; b < num_blocks; ++b) {
        for (int v = 1; v < MAXV; ++v) {
            block_tag[b][v] = v;
        }
    }

    cin >> q;
    while (q--) {
        int l, r, x, y;
        cin >> l >> r >> x >> y;
        l--; r--; 
        update_range(l, r, x, y);
    }

    for (int b = 0; b < num_blocks; ++b) {
        push_down(b);
    }

    for (int i = 0; i < n; ++i) {
        cout << arr[i] << (i == n - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}

问题二:带左下角禁区的网格路径计数

问题描述

在一个 $N \times M$ 的网格中,起点位于左上角 $(1,1)$,终点位于右下角 $(N,M)$。每次移动只能向右($x$ 坐标加 1)或向下($y$ 坐标加 1)。网格的左下角存在一个 $A \times B$ 的矩形禁区(即 $x \le A$ 且 $y \le B$ 的区域不可通行)。求从起点到终点的合法路径总数。

算法思路

在无限制的情况下,从 $(x_1, y_1)$ 到 $(x_2, y_2)$ 的格路数量可以通过组合数直接计算:$\binom{(x_2-x_1) + (y_2-y_1)}{x_2-x_1}$。

引入禁区限制后,由于移动方向仅限向右和向下,任何合法路径必然会在某个特定的纵坐标 $y$(其中 $y > B$)处穿过直线 $x = A$。我们可以利用这一几何特性,通过枚举路径穿过 $x = A$ 时的纵坐标 $y$ 来对路径进行分类。

对于每一个合法的穿越点 $(A, y)$($y \in [B+1, M]$),路径可以被分为两段:

  1. 从起点 $(1,1)$ 到穿越点 $(A, y)$ 的路径数。
  2. 从穿越点 $(A, y)$ 到终点 $(N,M)$ 的路径数。

将这两段的路径数相乘,并对所有可能的 $y$ 值求和,即可得到最终的合法路径总数。这种方法避免了复杂的容斥原理,直接通过分类讨论将问题转化为简单的组合数求和。

代码实现

#include <iostream>
#include <vector>

using namespace std;

const int MOD = 1e9 + 7;
const int MAXN = 200005;

long long factorial[MAXN];
long long inverse_factorial[MAXN];

long long power(long long base, long long exp) {
    long long res = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp % 2 == 1) res = (res * base) % MOD;
        base = (base * base) % MOD;
        exp /= 2;
    }
    return res;
}

void precompute() {
    factorial[0] = 1;
    for (int i = 1; i < MAXN; ++i) {
        factorial[i] = (factorial[i - 1] * i) % MOD;
    }
    inverse_factorial[MAXN - 1] = power(factorial[MAXN - 1], MOD - 2);
    for (int i = MAXN - 2; i >= 0; --i) {
        inverse_factorial[i] = (inverse_factorial[i + 1] * (i + 1)) % MOD;
    }
}

long long nCr(int n, int r) {
    if (r < 0 || r > n) return 0;
    return factorial[n] * inverse_factorial[r] % MOD * inverse_factorial[n - r] % MOD;
}

long long countPaths(int x1, int y1, int x2, int y2) {
    return nCr((x2 - x1) + (y2 - y1), x2 - x1);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    precompute();

    int N, M, A, B;
    if (!(cin >> N >> M >> A >> B)) return 0;

    long long total_paths = 0;

    for (int y = B + 1; y <= M; ++y) {
        long long paths_to_crossing = countPaths(1, 1, A, y);
        long long paths_from_crossing = countPaths(A, y, N, M);
        total_paths = (total_paths + paths_to_crossing * paths_from_crossing) % MOD;
    }

    cout << total_paths << "\n";

    return 0;
}

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

深入解析Node.js运行环境与异步I/O架构

深入解析Node.js运行环境与异步I/O架构

核心定义与价值Node.js本质上是一个JavaScript运行环境,而非编程语言或应用框架。它赋予了JavaScript脱离浏览器在服务端、命令行工具及网络应用中执行的能力。其核心意义在于:用单一语言打通前后端开发壁垒。基于事件驱动与非阻塞I/O的架构特性,Node.js在处理API网关、实时通信及微服务等I/O密集型场景时表现卓越,已成为现代后端工程的主流选择。浏览器沙箱限制1995年Java...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。