[洛谷刷题13]

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 1n,c1,c21000

输入格式

第一行三个整数 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名选手,需要分配到单人间或双人间
  • 双人间只能分配给同一队伍且性别相同的选手
  • 目标是最小化总成本

关键点

  1. 性别相同的选手:如果一个队伍中有至少两名选手性别相同,则可以组成双人间
  2. 成本比较:需要比较单人间和双人间的成本,选择最优分配方式

具体步骤

  1. 对于每个队伍,检查是否存在至少两名选手性别相同:
    • 如果存在,选择以下三种分配方式中的最小成本:
      • 全部单人间:成本为 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)(因为双人间无法使用)
  2. 将所有队伍的成本累加,得到最小总成本

时间复杂度

  • 每个队伍的处理时间为 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 1n,m20 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个点)不可经过
  • 需要计算所有可能的路径数

关键点

  1. 动态规划:使用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从 ( 0 , 0 ) (0,0) (0,0) ( i , j ) (i,j) (i,j) 的路径数
  2. 马的控制点处理:标记所有马的控制点为不可达
  3. 边界条件
    • 第一行和第一列的路径数只能由左侧或上方的点转移而来(如果可达)
    • 如果起点或终点被马控制,直接输出0

具体步骤

  1. 初始化 d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1
  2. 标记马的位置及其控制点为不可达( v i s [ i ] [ j ] = t r u e vis[i][j] = true vis[i][j]=true
  3. 处理边界条件:
    • 第一行:如果当前点可达,则路径数等于左侧点的路径数;否则为0
    • 第一列:同理,路径数等于上方点的路径数;否则为0
  4. 对于其他点 ( 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[i1][j]+dp[i][j1]
  5. 输出 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,m20,因此效率很高
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Almond_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值