P9363 [ICPC 2022 Xi’an R] Hotel
https://www.luogu.com.cn/problem/P9363
题目描述
你正在一个古代的酒店里为一场编程竞赛做志愿工作。酒店的历史可以追溯到秦朝,所以酒店不提供手机信号和自来水。你无法使用网络软件,不得不手动为参赛者分配房间。幸运的是,酒店拥有充足的房间,并且你有一台电脑帮你做一些计算。
共有 n n n 个队伍,每个队伍恰有 3 3 3 名选手。酒店有两种房间,单人间和双人间,分别可以容纳 1 1 1 和 2 2 2 名选手。为了避免使选手尴尬,如果两名选手分配到了同一个双人间,他们必须来自同一个队伍,并拥有相同的性别。
相同种类的房间的花费相同,但不同种类的房间花费可能不同。你需要计算主办方最少需要花多少钱。选手们已经在登记厅等候多时,而竞赛财务经理依靠你来节省开支,私吞剩下来的钱发大财。你需要尽快完成任务,否则财务经理将起诉你侵犯了他的名誉权!
1 ≤ n , c 1 , c 2 ≤ 1000 1\leq n, c_1, c_2\leq 1000 1≤n,c1,c2≤1000。
输入格式
第一行三个整数 n , c 1 , c 2 n, c_1, c_2 n,c1,c2,分别表示队伍数量,单人间单价和双人间单价。
接下来的 n n n 行,每行一个长度为 3 3 3 的字符串 S S S 表示一个队伍的参赛队员的性别。为了尊重人类的多样性, S S S 可能包含从 A \texttt A A 到 Z \texttt Z Z 的所有大写字母。
输出格式
输出一行一个整数表示分配房间的最小代价。
输入输出样例 #1
输入 #1
3 1 3
MMM
MMM
FFF
输出 #1
9
输入输出样例 #2
输入 #2
3 3 1
ABC
DEF
GHI
输出 #2
9
输入输出样例 #3
输入 #3
10 438 438
WWW
SOU
PUN
ETC
OME
CFI
NAL
GOO
DHO
TEL
输出 #3
12264
解题思路
问题分析
- 每个队伍有3名选手,需要分配到单人间或双人间
- 双人间只能分配给同一队伍且性别相同的选手
- 目标是最小化总成本
关键点
- 性别相同的选手:如果一个队伍中有至少两名选手性别相同,则可以组成双人间
- 成本比较:需要比较单人间和双人间的成本,选择最优分配方式
具体步骤
- 对于每个队伍,检查是否存在至少两名选手性别相同:
- 如果存在,选择以下三种分配方式中的最小成本:
- 全部单人间:成本为 3 × c 1 3 \times c_1 3×c1
- 一个双人间加一个单人间:成本为 c 2 + c 1 c_2 + c_1 c2+c1
- 两个双人间(如果可能):成本为 2 × c 2 2 \times c_2 2×c2
- 如果不存在(即所有选手性别均不同),则只能全部单人间:成本为 3 × min ( c 1 , c 2 ) 3 \times \min(c_1, c_2) 3×min(c1,c2)(因为双人间无法使用)
- 如果存在,选择以下三种分配方式中的最小成本:
- 将所有队伍的成本累加,得到最小总成本
时间复杂度
- 每个队伍的处理时间为 O ( 1 ) O(1) O(1),总时间复杂度为 O ( n ) O(n) O(n)
AC Code
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n, c1, c2;
cin >> n >> c1 >> c2;
int res = 0;
for(int i = 1; i <= n; ++i){
string s;
cin >> s;
if(s[0] == s[1] || s[0] == s[2] || s[1] == s[2]){
res += min({c1 * 3, c2 + c1, c2 * 2});
}
else{
res += min(c1, c2) * 3;
}
}
cout << res << endl;
return 0;
}
P1002 [NOIP 2002 普及组] 过河卒
https://www.luogu.com.cn/problem/P1002
题目描述
棋盘上 A A A 点有一个过河卒,需要走到目标 B B B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示, A A A 点 ( 0 , 0 ) (0, 0) (0,0)、 B B B 点 ( n , m ) (n, m) (n,m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A A A 点能够到达 B B B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入格式
一行四个正整数,分别表示 B B B 点坐标和马的坐标。
输出格式
一个整数,表示所有的路径条数。
输入输出样例 #1
输入 #1
6 6 3 3
输出 #1
6
说明/提示
对于 100 % 100 \% 100% 的数据, 1 ≤ n , m ≤ 20 1 \le n, m \le 20 1≤n,m≤20, 0 ≤ 0 \le 0≤ 马的坐标 ≤ 20 \le 20 ≤20。
【题目来源】
NOIP 2002 普及组第四题
解题思路
问题分析
- 卒从 ( 0 , 0 ) (0,0) (0,0) 出发,只能向右或向下移动,到达 ( n , m ) (n,m) (n,m)
- 马的位置 ( x , y ) (x,y) (x,y) 及其控制点(马能一步到达的8个点)不可经过
- 需要计算所有可能的路径数
关键点
- 动态规划:使用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从 ( 0 , 0 ) (0,0) (0,0) 到 ( i , j ) (i,j) (i,j) 的路径数
- 马的控制点处理:标记所有马的控制点为不可达
- 边界条件:
- 第一行和第一列的路径数只能由左侧或上方的点转移而来(如果可达)
- 如果起点或终点被马控制,直接输出0
具体步骤
- 初始化 d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1
- 标记马的位置及其控制点为不可达( v i s [ i ] [ j ] = t r u e vis[i][j] = true vis[i][j]=true)
- 处理边界条件:
- 第一行:如果当前点可达,则路径数等于左侧点的路径数;否则为0
- 第一列:同理,路径数等于上方点的路径数;否则为0
- 对于其他点
(
i
,
j
)
(i,j)
(i,j):
- 如果不可达,跳过
- 否则, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i][j-1] dp[i][j]=dp[i−1][j]+dp[i][j−1]
- 输出 d p [ n ] [ m ] dp[n][m] dp[n][m]
时间复杂度
- 时间复杂度为 O ( n × m ) O(n \times m) O(n×m),其中 n , m ≤ 20 n, m \leq 20 n,m≤20,因此效率很高
AC Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[30][30];
bool vis[30][30];
const array<int,2> dir[9]={{0,0},{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1}};
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m,x,y;
cin >> n >> m >> x >> y;
dp[0][0] = 1;
vis[x][y] = true;
for(int i=1;i<=8;i++){
if(x+dir[i][0] >=0 && x+dir[i][0]<=n && y+dir[i][1] >=0 && y+dir[i][1] <=m){
vis[x+dir[i][0]][y+dir[i][1]] = true;
}
}
for(int i=1;i<=n;i++){
if(vis[i][0]){
dp[i][0] = 0;
}
else{
dp[i][0] += dp[i-1][0];
}
}
for(int i=1;i<=m;i++){
if(vis[0][i]){
dp[0][i] = 0;
}
else{
dp[0][i] += dp[0][i-1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(vis[i][j]){
continue;
}
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
cout << dp[n][m];
return 0;
}