(题解)2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) I B J F H

I.这是一个签到题哦

原题指路:I-这是一个签到题哦_2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) (nowcoder.com)

题意(1s):

签到题,直接输出"Welcome to GDUTCPC!"就完事了。

代码:

#include<bits/stdc++.h>
using namespace std;

void solve() {
    cout << "Welcome to GDUTCPC!" << endl;
}

int main() {
    solve();
    return 0;
}

B.Austin Love Math

原题指路:B-Austin Love Math_2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) (nowcoder.com)

题意(1s):

已知 \(f(x) = \frac{x}{\sqrt{x^2 + 1}}\),\(f_{1}(x) = f[f(x)]\),\(f_{n+1}(x) = f[f_n(x)]\), 然后给定\(q(1 \leq q \leq 1e5)\)组\(x\)和\(t\),求每一个\(f_{t}(x)\)的值(注:选手输出与标准输出的相对误差或绝对误差小于\(10^{-4}\)即视为正确)。

思路:

我们推导一下公式找规律。

$$f_{1}(x) =\dfrac{\dfrac{x}{\sqrt{x^2 +1}}}{\sqrt{\dfrac{x^2}{x^2 + 1}+1}} = \frac{x}{\sqrt{2x^2+1}}$$

$$f_{2}(x) =\dfrac{\dfrac{x}{\sqrt{2x^2 +1}}}{\sqrt{\dfrac{x^2}{2x^2 + 1}+1}} = \frac{x}{\sqrt{3x^2+1}}$$

由此我们可以得出规律$$f_n(x) = \frac{x}{\sqrt{(n+1)x^2}+1}$$

代码:

#include<bits/stdc++.h>
using namespace std;

void solve() {
    double x;
    double t;
    cin >> x >> t;
    double ans = (t + 1.0) * x * x + 1.0;
    double res = x / sqrt(ans);
    cout << res << endl;
}

int main() {
    CaseT
    solve();
    return 0;
}

J.修棋盘

原题指路:J-修棋盘_2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) (nowcoder.com)

题意(1s):

给定一个\(n\times m (1 \leq n, m \leq 1000)\)大小的棋盘,一开始棋盘染色的时候并没有染成任意相邻两个格子不同颜色的棋盘(用\(0\)来表示当前棋盘格子是白色,\(1\)来表示当前棋盘格子是黑色)。现在找了一个修理工从坐标\((1,1)\)出发,每一次可以往上下左右相邻的格子走一步(不能出棋盘)。然后修理工有两种操作,当走到某个格子用了奇数步的时候,可以把这个格子修理成黑色;偶数步的时候,可以把这个格子修理成白色。修理工最多可以走\(998244353998244353\)步,然后求将棋盘染成任意相邻两个格子颜色不同的棋盘,修理工所需要的最少的操作次数,如果做不到,则输出\(-1\)。

思路:

①:首先我们注意到一个点,就是修理工能够将某个格子染成的颜色是固定的,不会因为修理工是如何走到这个格子而改变,而能染成什么颜色与这个点到原点的曼哈顿距离(两个点在标准坐标系上的绝对轴距总和)有关。假设有一个格子的坐标是\((i,j)\),那么当\(i+j\)是偶数的时候,修理工走到这个格子的步数就是偶数,修理工只能将这个点染成白色(即变成\(0\)),反之当\(i+j\)是奇数的时候,修理工只能将这个点染成黑色(即变成\(1\))

②:除去类似题目样例\(1\)的这种情况 $$\begin{vmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \\ 1 & 0 & 1  \end{vmatrix}$$ ,也就是\((1,1)\)上是\(1\)并且所有格子已经是满足了任意两个格子相间的情况外,其余的所有的棋盘,只要有一个需要染色的情况,最后一定会染色成 $$\begin{vmatrix} 0 & 1 & 0 \\ 1 & 0 & 1 \\ 0 & 1 & 0  \end{vmatrix}$$这种情况,因为正如上面①所说的那样,某个具体的格子只能染成某种具体的颜色,或者再举个例子,对于下述这种情况$$\begin{vmatrix} 1 & 0 & 1 \\ 0 & 1 & 0 \\ 1 & 1 & 1  \end{vmatrix}$$我们是无法将\((1,2)\)这个格子变成\(0\)的,因为修理工走到这个点的时候一定是奇数步,修理工只能将这个格子变成\(1\),我们能做的只有把其他的格子给全部染掉,最后棋盘一定会变成$$\begin{vmatrix} 0 & 1 & 0 \\ 1 & 0 & 1 \\ 0 & 1 & 0  \end{vmatrix}$$的情况。

③:由于数据给的是\(1 \leq n, m \leq 1000\)的棋盘大小,走“S”型最多只用\(n \times m  \leq 1e6\)步,远远小于\(998244353998244353\),所以是不存在做不到的情况。

(这个数据的唯一作用就是让笔者在做题的时候怀疑人生,看题不走心以为最后要求的是修理工将棋盘染色所需要的最小步数,然后看见榜单别人刷刷刷的过题然后自己做不出来。最后发现求的是最少操作步数,然后开始怀疑自己的眼睛。嘤嘤嘤~)

我们做题的时候只需要判断每个格子的颜色是什么,然后统计与格子原本应该的颜色不同的格子数目,最后输出即可。特别的,当所有格子都不相同,也就是不同的格子的数目等于棋盘上所有的格子的数目的时候,这种情况就是②中的第一个图,所有的格子已经两两相间了,此时不需要再涂色了。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1100;

string a[N];

void solve() {
    int n, m; cin >> n >> m;

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

    int ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (a[i][j] == '1' && (i + j) % 2 == 0) {
                ans++;
            }
            if (a[i][j] == '0' && (i + j) % 2 == 1) {
                ans++;
            }
        }
    }

    if (ans == n * m) cout << 0 << endl;
    else cout << ans << endl;

}

int main() {
    solve();
    return 0;
}

F.魔王大人的打工日常

原题指路:F-魔王大人的打工日常_2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) (nowcoder.com)

题意(1s):

给定\(n\)个盘子,进行\(m\)次操作(\(1 \leq n,m \leq 1e5\))。盘子初始排列是\(P=(1,2,,…,n-1,n)\),每次操作选定第\(l\)个到第\(r\)个碟子进行\(k\)次循环右移(保证\(l \leq r\)且\(k\)在 int 范围内)。(注:对在第\(l\)个到第\(r\)个碟子这个区间的碟子\(P' = (p_l,p_{l+1},,…,p_{r-1},p_r)\)的一次循环操作使得\(P' = (p_r,p_{l},p_{l+1},…,p_{r-1})\),再通俗点讲就是,所有数字往右移一个,最右边的移到最左边去。)然后求出每次操作过后序列中的逆序对(满足\(i < j\)且\(a_i > a_j\))的奇偶性,如果是偶数就输出“hao”,奇数的话就输出“huai”。

思路:

这道题需要用到线性代数的知识,这里只大致引入概念,不作相关证明。如果一个排序(指有\(n\)个互不相同的数,且大小范围都在\(1 \sim n\)之间),他的逆序对的个数恰为偶数,那么我们称这个排序为偶排序,反之如果恰有奇数个逆序对,那么我们称之为奇排序。如果我们将排序中的任意两个数交换位置,那么这个排序的奇偶性改变(也就是交换一次后,奇排序就会变成偶排序,偶排序就会变成奇排序)。

我们知道这个结论之后,就可以开始做题了,假设我们将\(n\)个数进行循环右移,可以视为将最右边的那个数和左边一个的数进行\(n-1\)次交换的操作,也就是奇偶性更改\(n-1\)次。所以我们只需要记录一下,每次操作奇偶性更改的次数,看他更改次数的奇偶性即可。

代码:

#include<bits/stdc++.h>
using namespace std;

void solve() {
    int n, q;
    cin >> n >> q;

    int ans = 0;
    while(q--) {
        int l, r, t;
        cin >> l >> r >> t;
        ans += (r - l) * t;

        if (ans & 1) cout << "huai" << endl;
        else cout << "hao" << endl;
    }

}

int main() {
    solve();
    return 0;
}

H.合成大西瓜

原题指路:H-合成大西瓜_2023年“华为”杯广东工业大学第十七届程序设计竞赛(同步赛) (nowcoder.com)

题意(1s):

有一个垂直管道,上端开口,下端封顶,管道宽度只能放一个球,长度无限,当两个一级球相邻时,就可以变成一个二级球,当两个二级球相邻时,就可以变成一个三级球,当两个三级球相邻时,就可以变成一个四级球。每次操作都会从管道上方随机放入一个一、二或三级球,概率都是三分之一。初始时,管道中有\(n(0 \leq n \leq 1e5)\)个球,然后从下到上依次给出每个球的种类(保证任意相邻的两个球种类不相等),然后求最后管道中合成出四级球的期望次数。

思路:

首先我们应当注意到如果上面的球的级数大于其下方的球的级数,那么下面的球将毫无作用, 因为合成四级球的时候,一定会在上面先和成出四级球,而不会用到下面的小球。因此看似管道中的球能有很多种放法,但都能够归为八种基本状态(两条竖线中夹着的就是小球的种类,下方的字母表示设这种情况最后合成四级球的期望次数)。

\(\begin{Vmatrix}①\end{Vmatrix}\)          \(\begin{Vmatrix}②\end{Vmatrix}\)          \(\begin{Vmatrix}①\\②\end{Vmatrix}\)          \(\begin{Vmatrix}①\\②\\③\end{Vmatrix}\)          \(\begin{Vmatrix}②\\③\end{Vmatrix}\)          \(\begin{Vmatrix}①\\③\end{Vmatrix}\)          \(\begin{Vmatrix}③\end{Vmatrix}\)          \(\begin{Vmatrix}  \end{Vmatrix}\)

   a                  b                 c                  d                  e                  f                  g               h

得出了这八种状态之后(注意,第八种情况比较特殊,容易忘记考虑,就是最开始的时候有可能管道中一个球都没有的情况),我们就可以开始列方程了,我们以第一种状态和第五种状态为例(我认为这两种情况较为典型,举例能相对来说涵盖各种情况方便理解)。

$$a: a = \frac{1}{3}(1 + b) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)$$

有\(\frac{1}{3}\)的概率落下一级球,然后就会合成二级球,变成状态\(2\),因此这一部分的期望是,走上\(1\)步,再加上变成状态\(2\)变成四级球的期望。然后另有\(\frac{1}{3}\)的概率,落下二级球,此时上面的二级球比下面的一级球大,所以下面的一级球就没有用了,因此仍然变成状态\(2\),最后\(\frac{1}{3}\)的概率是落下三级球,此时就变成了状态\(8\)。

$$e: e = \frac{1}{3}(1 + d) + \frac{1}{3}(1 + 0) + \frac{1}{3}(1 + g)$$

对于第五种情况,有\(\frac{1}{3}\)的概率落下一级球,然后就会落在上面,变成状态\(4\),然后另有\(\frac{1}{3}\)的概率,落下二级球,于是两个二级球合成变成三级球,然后两个三级球合成为四级球,完成目标,因此这一部分的期望就是走这一步,最后\(\frac{1}{3}\)的概率是落下三级球,此时这个三级球比他下面的二级球大,于是又变成了状态\(8\)。

最后我们一共可以列出如下八个方程:

$$\begin{cases}a = \frac{1}{3}(1 + b) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)\\b = \frac{1}{3}(1 + c) + \frac{1}{3}(1 + g) + \frac{1}{3}(1 + g)\\c = \frac{1}{3}(1 + g) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)\\d = \frac{1}{3}(1 + 0) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)\\e = \frac{1}{3}(1 + d) + \frac{1}{3}(1 + 0) + \frac{1}{3}(1 + g)\\f = \frac{1}{3}(1 + e) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)\\g = \frac{1}{3}(1 + f) + \frac{1}{3}(1 + e) + \frac{1}{3}(1 + 0)\\h = \frac{1}{3}(1 + a) + \frac{1}{3}(1 + b) + \frac{1}{3}(1 + g)\end{cases}$$

化简后即为:

$$\begin{cases}3a - 2b - g = 3\\3b - c - 2g = 3\\3c - b - 2g = 3\\3d - b - g = 3\\3e - d - g = 3\\3f - b - e - g = 3\\3g - e - f = 3\\3h - a - b - g = 3\end{cases}$$

于是接下来的工作就是解这个八元一次方程组了,首先可以肯定的是,这个方程是一定有解,因为每个状态最后都是可以合成出四级球的,同时这个解一定是唯一解,因为如果存在多组解,那么某一种情况的期望就会出现两个以上,而每种状态最后合成出四级球的概率一定是固定的,不可能存在一会是这个值,一会是那个值的情况。

解这个方程组,一种是可以直接手算硬撕它(反正一定是能解出来的),另一种方法就是高斯消元,这里又要用到线性代数的知识,这也是我最后化简方程将常数项分离放到右边,并且把未知数的系数都变成整数的原因,这样就可以使用高斯消元的板子,来帮我们解出每个未知数的大小了。

高斯消元代码:

#include<bits/stdc++.h>
using namespace std;

//高斯消元
//枚举每一列的C
// ① 找到绝对值最大的一行 (为了防止精度丢失)
// ② 将该行换到最上面
// ③ 将该行第一个数变成1
// ④ 将下面所有行的第C列清成0

const int N = 110;
const double eps = 1e-8;
int n;
double a[N][N];

int gauss() {
    int c, r;
    for (c = 0, r = 0; c < n; c++) { //枚举每一列,找到绝对值最大的这一行
        int t = r;
        for (int i = r; i < n; i++) { //找到绝对值最大的这一行是哪一行
            if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
        }
        if (fabs(a[t][c]) < eps) continue; //如果是零的话,就不用操作了

        for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); //交换,把绝对值最大的一行换到上面
        for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; //把第一个数变成1.后面的数跟着变,当然第一个数要最后一个更新成1
        for (int i = r + 1; i < n; i++) {
            if (fabs(a[i][c]) > eps) {
                for (int j = n; j >= c; j--) {
                    a[i][j] -= a[r][j] * a[i][c];
                }
            }
        }
        
        r++;
    }

    if (r < n) {
        for (int i = r; i < n; i++) {
            if (fabs(a[i][n]) > eps) {
                return 2;
            }
        }
        return 1;
    }

    for (int i = n - 1; i >= 0; i--) {
        for (int j = i + 1; j < n; j++) {
            a[i][n] -= a[i][j] * a[j][n];
        }
    }

    return 0;
}

void solve() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n + 1; j++) {
            cin >> a[i][j];
        }
    }

    int t = gauss();

    if (t == 0) {
        for (int i = 0; i < n; i++) {
            if (fabs(a[i][n]) < eps) a[i][n] = 0;
            printf("%.8lf\n", a[i][n]);
        }
    }
    else if (t == 1) cout << "Infinite group solutions" << endl;
    else cout << "No solution" << endl;

}

int main() {
    solve();
    return 0;
}

高斯消元输入和输出:

我们在把每个未知数都求出来后,也就算出来了每种情况的期望合成次数,因此我们可以将打出来的表写在另外一篇代码中,然后在这篇代码中判断初始时管道中的球是什么情况。

最终(你要交上去的)代码:

代码中的\(flag\)表示的是第几种情况。写到这里的时候我已经有点神志不清了,所以就直接暴力到底了,反正都打表了,所以是直接判断管道中的球是零个、一个、两个、三个以上,以及各自可能存在的情况,暴力 else-if 语句去判断管道中的球的初始状态了。

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int a[N];

void solve() {
    int n; cin >> n;
    int flag = 0;

    if (n == 0) {
        flag = 8;
    }

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

    if (n == 1) {
        if (a[1] == 1) {
            flag = 1;
        }
        if (a[1] == 2) {
            flag = 2;
        }
        if (a[1] == 3) {
            flag = 7;
        }
    }

    if (n == 2) {
        if (a[2] == 3) {
            flag = 7;
        }
        if (a[2] == 2 && a[1] == 3) {
            flag = 5;
        }
        if (a[2] == 2 && a[2] == 1) {
            flag = 2;
        }
        if (a[2] == 1 && a[1] == 2) {
            flag = 3;
        }
        if (a[2] == 1 && a[1] == 3) {
            flag = 6;
        }
    }

    if (n >= 3) {
        if (a[n] == 3) {
            flag = 7;
        }
        if (a[n] == 2 && a[n - 1] == 1) {
            flag = 2;
        }
        if (a[n] == 2 && a[n - 1] == 3) {
            flag = 5;
        }
        if (a[n] == 1 && a[n - 1] == 3) {
            flag = 6;
        }
        if (a[n] == 1 && a[n - 1] == 2) {
            if (a[n - 2] == 3) {
                flag = 4;
            }
            if (a[n - 2] == 1) {
                flag = 3;
            }
        }
    }
    
    double f[9] = {0.0, 6.08139535, 5.58139535, 5.58139535, 4.22093023, 3.76744186, 5.47674419, 4.08139535, 6.24806202};
    printf("%.8lf\n", f[flag]);

}

int main() {
    solve();
    return 0;
}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值