P1220关路灯mjhcsp

标题

题目

P1220 关路灯


题目描述

某一村庄在一条路线上安装了 n n n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。

为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。

现在已知老张走的速度为 1 m / s 1m/s 1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位: m m m)、功率( W W W),老张关灯所用的时间很短而可以忽略不计。

请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。

输入格式

第一行是两个数字 n n n(表示路灯的总数)和 c c c(老张所处位置的路灯号);

接下来 n n n 行,每行两个数据,表示第 1 1 1 盏到第 n n n 盏路灯的位置和功率。数据保证路灯位置单调递增。

输出格式

一个数据,即最少的功耗(单位: J J J, 1 J = 1 W × s 1J=1W\times s 1J=1W×s)。

输入输出样例 #1

输入 #1

复制代码
5 3
2 10
3 20
5 20
6 30
8 10

输出 #1

复制代码
270

说明/提示

样例解释

此时关灯顺序为 3 4 2 1 5

数据范围

1 ≤ n ≤ 50 1\le n\le50 1≤n≤50, 1 ≤ c ≤ n 1\le c\le n 1≤c≤n, 1 ≤ W i ≤ 100 1\le W_i \le 100 1≤Wi≤100。

解题思路

这道题是典型的动态规划 问题,核心思路 是通过状态定义转移方程 ,找到关灯的最优顺序 ,使得总耗电量最小

关键观察

耗电量计算:路灯的耗电量 = 功率 × 点亮时间 。点亮时间是从老张开始关灯到该路灯被关闭的总时间(包括走路时间)。

最优子结构:老张每次只能向左或向右关灯,且已关闭的路灯一定是连续的区间(因为如果中间有未关闭的灯,绕路去关其他灯会增加总时间)。因此,状态可以用 "当前关闭的路灯区间 [l, r]" 和 "老张当前所在位置(左端点 l 或右端点 r)" 来表示。

状态转移:对于区间 [l, r],老张要么从左端点 l 扩展到 l-1,要么从右端点 r 扩展到 r+1。转移时需要计算走路的时间,并乘以当前未关闭路灯的总功率(因为这些灯在走路过程中仍在耗电)。

具体步骤

输入处理与预处理:

读取路灯数量 n 和老张初始位置 c(注意:题目中路灯号从 1 开始,需转换为 0 索引方便数组操作)。

读取每盏路灯的位置和功率,存储在数组中。

计算功率前缀和数组,方便快速查询未关闭路灯的总功率。

状态定义:

dp[l][r][0]:关闭区间 [l, r] 的路灯后,老张在左端点 l 时的最小耗电量。

dp[l][r][1]:关闭区间 [l, r] 的路灯后,老张在右端点 r 时的最小耗电量。

初始化:

初始状态是老张只关闭了自己所在的路灯(区间 [c, c]),此时耗电量为 0,即 dp[c][c][0] = dp[c][c][1] = 0。

状态转移:

对于区间长度 k(从 1 到 n-1),枚举所有可能的左端点 l,右端点 r = l + k。

计算从 l+1 到 r 扩展到 l(老张从 l+1 走到 l)的耗电量:dp[l][r][0] = min(dp[l+1][r][0] + (pos[l+1] - pos[l]) * power_sum, dp[l+1][r][1] + (pos[r] - pos[l]) * power_sum)。

计算从 l 到 r-1 扩展到 r(老张从 r-1 走到 r)的耗电量:dp[l][r][1] = min(dp[l][r-1][0] + (pos[r] - pos[l]) * power_sum, dp[l][r-1][1] + (pos[r] - pos[r-1]) * power_sum)。

其中 power_sum 是当前未关闭路灯的总功率,即总功率减去区间 [l, r] 的功率和。

结果:

最终答案是关闭所有路灯(区间 [0, n-1])后,老张在左端点或右端点的最小耗电量,即 min(dp[0][n-1][0], dp[0][n-1][1])。

代码

cpp 复制代码
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXM = 60;  // 适配 n≤50 的数据范围
int a[MAXM];          // 路灯位置(1-based索引)
int b[MAXM];          // 路灯功率(1-based索引)
int sum[MAXM];        // 功率前缀和:sum[i] = b[1]+b[2]+...+b[i]
int f[MAXM][MAXM][2]; // 动态规划数组
int n, c;             // n:路灯总数,c:初始路灯号(1-based)

// 自定义min函数,提升效率
int min(int x, int y) {
    return x < y ? x : y;
}

int main() {
    // 输入数据
    scanf("%d%d", &n, &c);
    sum[0] = 0;
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &a[i], &b[i]);
        sum[i] = sum[i-1] + b[i]; // 计算前缀和
    }

    // 初始化:只关闭初始位置的路灯,功耗为0
    memset(f, 0x3f, sizeof(f)); // 初始化为极大值(0x3f对应十进制1061109567,足够大)
    f[c][c][0] = f[c][c][1] = 0;

    // 枚举关闭的路灯区间长度l(从2到n,逐步扩展)
    for (int l = 2; l <= n; ++l) {
        // 枚举区间左端点i,右端点j = i + l - 1
        for (int i = 1; i + l - 1 <= n; ++i) {
            int j = i + l - 1;

            // 状态转移:关闭区间[i,j]后,人在左端点i
            // 两种来源:1. 从[i+1,j]的左端点i+1走到i;2. 从[i+1,j]的右端点j走到i
            // 功耗 = 之前的功耗 + 走路时间 × 未关闭路灯的总功率
            f[i][j][0] = min(
                f[i+1][j][0] + (a[i+1] - a[i]) * (sum[n] - (sum[j] - sum[i])),
                f[i+1][j][1] + (a[j] - a[i]) * (sum[n] - (sum[j] - sum[i]))
            );

            // 状态转移:关闭区间[i,j]后,人在右端点j
            // 两种来源:1. 从[i,j-1]的左端点i走到j;2. 从[i,j-1]的右端点j-1走到j
            f[i][j][1] = min(
                f[i][j-1][0] + (a[j] - a[i]) * (sum[n] - (sum[j-1] - sum[i-1])),
                f[i][j-1][1] + (a[j] - a[j-1]) * (sum[n] - (sum[j-1] - sum[i-1]))
            );
        }
    }

    // 最终答案:关闭所有路灯(区间[1,n])后,人在左端点或右端点的最小功耗
    printf("%d\n", min(f[1][n][0], f[1][n][1]));
    return 0;
}

代码核心解释(与原思路完全一致)

数组定义:

a[i]:第 i 盏路灯的位置(1-based 索引,符合题目输入习惯)

b[i]:第 i 盏路灯的功率

sum[i]:前缀和数组,快速计算区间功率和(sum[j]-sum[i-1] 是区间 [i,j] 的总功率)

f[i][j][0]:关闭区间 [i,j] 的路灯后,人在左端点 i 时的最小功耗

f[i][j][1]:关闭区间 [i,j] 的路灯后,人在右端点 j 时的最小功耗

关键公式(未关闭路灯的总功率):

当扩展到区间 [i,j] 时,未关闭的路灯功率 = 总功率(sum [n]) - 已关闭区间的功率

例如:计算f[i][j][0]时,已关闭区间是 [i,j],功率和为sum[j]-sum[i],因此未关闭功率为sum[n] - (sum[j]-sum[i])

状态转移逻辑:

扩展区间时,走路时间 = 两点间距离(位置差),因为速度是 1m/s

每一步的功耗增加 = 走路时间 × 未关闭路灯的总功率(这些灯在走路过程中持续耗电)

初始化与结果:

初始状态:只关闭初始位置 c 的路灯,功耗为 0(f[c][c][0] = f[c][c][1] = 0)

最终结果:关闭所有路灯(区间 [1,n])后,取人在左端点或右端点的最小功耗

输入输出样例验证

输入:

plaintext

5 3

2 10

3 20

5 20

6 30

8 10

输出:270,与题目样例一致,说明代码正确性。

效率说明

时间复杂度:O (n²),n≤50 时,循环次数仅 50×50=2500 次,效率极高

空间复杂度:O (n²),使用的数组大小为 60×60×2=7200,远低于内存限制

自定义 min 函数避免了标准库函数的额外开销,进一步提升运行速度

相关推荐
kyle~1 小时前
算法与数据结构---并查集(Union-Find)
数据结构·c++·算法
茉莉玫瑰花茶1 小时前
ProtoBuf - 1 - 下载和环境配置
开发语言·c++·protobuf
_OP_CHEN1 小时前
C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路
开发语言·c++
爱学习的小邓同学1 小时前
C++ --- map/set的使用
开发语言·c++
MSTcheng.1 小时前
【C++进阶】继承(下)——挖掘继承深处的奥秘!
开发语言·c++
学困昇1 小时前
Linux基础开发工具(上):从包管理到“进度条”项目实战,掌握 yum/vim/gcc 核心工具
linux·运维·开发语言·数据结构·c++·vim
ALex_zry2 小时前
Rust语言基础分析与C++对比:系统编程的现代演进
java·c++·rust
Molesidy2 小时前
【QT】【C++】基于QT的多线程分别管理GUI和运算任务
开发语言·c++·qt
Vanranrr2 小时前
Python vs PowerShell:自动化 C++ 配置文件的两种实现方案
c++·python·自动化