当前位置: 首页 > news >正文

UVa 12991 Game Rooms

题目分析

问题描述

公司新建了一栋 NNN 层的摩天大楼,但发现每层只能建造一个游戏室(乒乓球室或台球室)。整栋大楼必须至少有一个乒乓球室和一个台球室。

已知每层有 TiT_iTi 个乒乓球玩家和 PiP_iPi 个台球玩家。目标是为每层分配合适的游戏室类型,使得所有员工到最近的对应类型游戏室的距离之和最小。距离定义为楼层差的绝对值。

输入格式

  • 第一行:测试用例数量 TTT (1≤T≤1001 \leq T \leq 1001T100)
  • 每个测试用例:
    • 第一行:楼层数 NNN (2≤N≤40002 \leq N \leq 40002N4000)
    • 接下来 NNN 行:每行两个整数 TiT_iTiPiP_iPi (1≤Ti,Pi≤1091 \leq T_i, P_i \leq 10^91Ti,Pi109),表示第 iii 层的乒乓球和台球玩家数量

输出格式

对于每个测试用例,输出一行 Case #x: y,其中 xxx 是测试用例编号,yyy 是最小总距离。

样例分析

样例输入:

1
2
10 5
4 3

最优分配:

  • 第一层:乒乓球室
  • 第二层:台球室

距离计算:

  • 第一层的 555 个台球玩家需要到第二层,距离为 111,总距离 5×1=55 \times 1 = 55×1=5
  • 第二层的 444 个乒乓球玩家需要到第一层,距离为 111,总距离 4×1=44 \times 1 = 44×1=4
  • 总距离:5+4=95 + 4 = 95+4=9

解题思路

关键观察

  1. 交替段结构:最优解中,同类型的游戏室会形成连续的段,不同类型的段交替出现。
  2. 距离计算:对于类型为 ttt 的连续段 [L,R][L, R][L,R],段内类型为 1−t1-t1t 的玩家需要去段外的游戏室:
    • 去左边的 L−1L-1L1 层(类型 1−t1-t1t
    • 或去右边的 R+1R+1R+1 层(类型 1−t1-t1t
  3. 单调性:在连续段 [L,R][L, R][L,R] 中,存在一个分割点 midmidmid,使得:
    • [L,mid][L, mid][L,mid] 的玩家去左边的游戏室更近
    • [mid+1,R][mid+1, R][mid+1,R] 的玩家去右边的游戏室更近

动态规划设计

定义状态:

  • dp[i][t]dp[i][t]dp[i][t]:前 iii 层的最小总距离,且第 iii 层的游戏室类型为 ttt000 表示乒乓球,111 表示台球)

状态转移:

  • 枚举前一个段的结束位置 jjj0≤j<i0 \leq j < i0j<i
  • 当前段为 [j+1,i][j+1, i][j+1,i],类型为 ttt
  • 计算段内另一种类型玩家的最小距离和:
    • 如果 j=0j = 0j=0(第一段):所有玩家只能去右边的 i+1i+1i+1
    • 如果 i=Ni = Ni=N(最后一段):所有玩家只能去左边的 jjj
    • 否则:使用双指针找到最优分割点 midmidmid[j+1,mid][j+1, mid][j+1,mid] 去左边,[mid+1,i][mid+1, i][mid+1,i] 去右边

优化技巧

  1. 前缀和预处理:快速计算区间内玩家数量和加权距离
  2. 双指针优化:利用决策单调性,将内层循环从 O(N)O(N)O(N) 优化到 O(1)O(1)O(1)
  3. 边界处理:确保至少有两种类型的游戏室

算法复杂度

  • 时间复杂度O(N2)O(N^2)O(N2),通过双指针优化避免了内层循环
  • 空间复杂度O(N)O(N)O(N),使用前缀和数组和 dpdpdp 数组

代码实现

// Game Rooms
// UVa ID: 12991
// Verdict: Accepted
// Submission Date: 2025-10-24
// UVa Run Time: 0.550s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;typedef long long ll;const ll INF = 1e18;  // 定义无穷大/*** 计算区间 [L, R] 内所有玩家到目标楼层的距离和* @param L 区间左端点* @param R 区间右端点  * @param targetFloor 目标楼层* @param countSum 玩家数量的前缀和数组* @param weightedSum 玩家数量×楼层号的前缀和数组* @return 总距离代价*/
ll calculateCost(int L, int R, int targetFloor, const vector<ll>& countSum, const vector<ll>& weightedSum) {if (L > R) return 0;  // 空区间代价为 0ll cnt = countSum[R] - countSum[L - 1];  // 区间内玩家总数ll weighted = weightedSum[R] - weightedSum[L - 1];  // 区间内玩家楼层加权和return abs(weighted - targetFloor * cnt);  // 计算总距离
}int main() {ios_base::sync_with_stdio(false);cin.tie(NULL);int testCases;cin >> testCases;for (int caseNum = 1; caseNum <= testCases; caseNum++) {int totalFloors;cin >> totalFloors;// 存储每层的乒乓球和台球玩家数量,下标从 1 开始vector<ll> tableTennisPlayers(totalFloors + 2, 0);vector<ll> poolPlayers(totalFloors + 2, 0);for (int i = 1; i <= totalFloors; i++) {cin >> tableTennisPlayers[i] >> poolPlayers[i];}// 前缀和数组初始化vector<ll> sumTableTennis(totalFloors + 2, 0);  // 乒乓球玩家前缀和vector<ll> sumPool(totalFloors + 2, 0);         // 台球玩家前缀和vector<ll> sumTableTennisWeighted(totalFloors + 2, 0);  // 乒乓球玩家×楼层号前缀和vector<ll> sumPoolWeighted(totalFloors + 2, 0);         // 台球玩家×楼层号前缀和// 计算前缀和for (int i = 1; i <= totalFloors; i++) {sumTableTennis[i] = sumTableTennis[i - 1] + tableTennisPlayers[i];sumPool[i] = sumPool[i - 1] + poolPlayers[i];sumTableTennisWeighted[i] = sumTableTennisWeighted[i - 1] + tableTennisPlayers[i] * i;sumPoolWeighted[i] = sumPoolWeighted[i - 1] + poolPlayers[i] * i;}// DP 数组:dp[i][type] 表示前 i 层,第 i 层类型为 type 的最小代价vector<vector<ll>> dp(totalFloors + 1, vector<ll>(2, INF));dp[0][0] = dp[0][1] = 0;  // 初始化// 主 DP 循环for (int i = 1; i <= totalFloors; i++) {for (int currentType = 0; currentType < 2; currentType++) {// 根据当前类型选择对应的前缀和数组const vector<ll>& otherCount = (currentType == 0) ? sumPool : sumTableTennis;const vector<ll>& otherWeighted = (currentType == 0) ? sumPoolWeighted : sumTableTennisWeighted;// 双指针优化:optimalMid 记录当前最优分割点int optimalMid = 0;for (int prevEnd = 0; prevEnd < i; prevEnd++) {int L = prevEnd + 1;  // 当前段起始位置int R = i;            // 当前段结束位置// 检查是否整栋楼只有一种类型(无效情况)if (prevEnd == 0 && i == totalFloors) {continue;}ll cost = 0;  // 当前段的代价if (prevEnd == 0) {// 第一段:所有玩家只能去右边的 R+1 层cost = calculateCost(L, R, R + 1, otherCount, otherWeighted);} else if (i == totalFloors) {// 最后一段:所有玩家只能去左边的 prevEnd 层cost = calculateCost(L, R, prevEnd, otherCount, otherWeighted);} else {// 中间段:使用双指针找到最优分割点// 随着 R 增大,optimalMid 单调不减while (optimalMid < R - 1) {// 计算当前分割点的代价ll currentCost = calculateCost(L, optimalMid, prevEnd, otherCount, otherWeighted) +calculateCost(optimalMid + 1, R, R + 1, otherCount, otherWeighted);// 计算下一个分割点的代价ll nextCost = calculateCost(L, optimalMid + 1, prevEnd, otherCount, otherWeighted) +calculateCost(optimalMid + 2, R, R + 1, otherCount, otherWeighted);// 如果下一个分割点不更优,则停止移动if (nextCost >= currentCost) break;optimalMid++;}// 使用最优分割点计算总代价cost = calculateCost(L, optimalMid, prevEnd, otherCount, otherWeighted) +calculateCost(optimalMid + 1, R, R + 1, otherCount, otherWeighted);}// 更新 DP 状态dp[i][currentType] = min(dp[i][currentType], dp[prevEnd][1 - currentType] + cost);}}}// 输出结果:取两种类型的较小值ll answer = min(dp[totalFloors][0], dp[totalFloors][1]);cout << "Case #" << caseNum << ": " << answer << "\n";}return 0;
}

总结

本题通过动态规划结合双指针优化,高效地解决了游戏室分配问题。关键点在于识别连续段的交替结构,并利用前缀和快速计算距离代价。算法在 O(N2)O(N^2)O(N2) 时间内解决了问题,适用于 N≤4000N \leq 4000N4000 的数据规模。

http://www.dtcms.com/a/525234.html

相关文章:

  • 2012年下半年试题一:论基于架构的软件设计方法及应用
  • 7. Python 列表:从概念本质到实战应用
  • Windows 安装 postGreSQL 数据库
  • 双堆法求数据流的中位数
  • 平面设计师灵感网站开发三味
  • 使用Requests和正则表达式实现起点中文网小说爬取
  • 擅自使用他人产品做网站宣传网站后台编辑器上传不了图片
  • argocd发布实现预检和后检能力
  • 受欢迎的免费网站建设游戏开发和网站开发哪个好玩
  • 网站建设 软件开发的公司办公网站模板
  • 内蒙古知名网站建设wordpress 摄影博客
  • 掌握 Gemini CLI:自定义命令 (Slash Commands)
  • 平面设计师常用网站铁岭网站建设公司
  • 个人网站做推广wordpress增加论坛
  • oracle存储过程详解
  • 多线程六脉神剑第三剑:信号量 (Semaphore)
  • 网站外链怎么购买网站建设方案计划书
  • 在C#中详细介绍一下Visual Studio中如何使用数据可视化工具
  • TDengine 数据函数 ROUND 用户手册
  • 网上做网站怎么做下拉菜单广告设计软件下载
  • LeetCode 406 - 根据身高重建队列
  • ELK运维之路(异常日志告警)
  • 从零开始的C++学习生活 14:map/set的使用和封装
  • 只做同城交易的网站wordpress自定义文章类型分类模板
  • 做外贸要看哪些网站阿里云做网站怎么样
  • 常州市经开区建设局网站东莞h5网站建设
  • 个人做网站需要多少钱seo网址大全
  • Python进阶(11-2):Python游戏编程-FlappyBird
  • 网站制作怎么做网站建设平台多少钱
  • [OP-Agent] 可扩展架构 | 插件管理器plugins.go