【Acwing 周赛复盘】第89场周赛复盘(2023.2.4)

【Acwing 周赛复盘】第89场周赛复盘(2023.2.4)

周赛复盘 ✍️

本周个人排名:1302/2232

AC情况:2/3

周赛当天晚上,博主出门在外,未实时参加,是在之后定时自测的(终于把春节欠的债还完了😂)

这也是博主参加的第四次周赛,感觉做题比之前三次都顺畅了不少(也可能是这次的难度较低),20min里 AC 了 2 题,估计现场参加的话能排 270 名左右,对自己来说还是有进步的。🍉

T1 签到题,枚举即可。✅

T2 直接模拟,时间复杂度 O ( n 3 ) O(n^3) O(n3),勉强通过。(后来看y总直播讲解貌似也是直接 O ( n 3 ) O(n^3) O(n3) 暴力过,因为题目数据范围较小)✅

T3 一眼DP,但是数学层面的性质较难想到,导致状态转移方程始终差一点,未能AC(经过y总讲解后明白了如何从数学层面推导相关性质,值得积累)❌

希望未来也能继续努力,紧跟大佬们的步伐,继续加油 💪

image-20230207114058201


周赛信息 📚

时间:2023年 2 月 4 日 19:00-20:15

竞赛链接https://www.acwing.com/activity/content/2854/

y总直播间http://live.bilibili.com/21871779

y总录播讲解视频【AcWing杯 - 第 89 场周赛讲解】


题目列表 🧑🏻‍💻

题目名称原题链接视频讲解难度
4803. 满足的数原题链接视频链接简单 🟢
4804. 构造矩阵原题链接视频链接中等 🟡
4805. 加减乘原题链接视频链接困难 🔴

题解 🚀

【题目A】满足的数

【题目描述】

给定 n n n 个不超过 5 5 5 的正整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an

不妨设 S = a 1 + a 2 + ⋯ + a n S=a_1+a_2+\dots+a_n S=a1+a2++an

请你统计,一共有多少个不同的整数 x x x 能够同时满足以下所有条件:

  • 1 ≤ x ≤ 5 1 \le x \le 5 1x5
  • ( S + x ) m o d ( n + 1 ) ≠ 1 (S+x)mod(n+1) \ne 1 (S+x)mod(n+1)=1
【输入】

第一行包含整数 n n n

第二行包含 n n n 个正整数 a 1 , a 2 , … , a n a_1,a_2,\dots ,a_n a1,a2,,an

【输出】

一个整数,表示满足条件的 x x x 的数量。

【数据范围】

3 3 3 个测试点满足 1 ≤ n ≤ 2 1 \le n \le 2 1n2

所有测试点满足 1 ≤ n ≤ 100 1 \le n \le100 1n100 1 ≤ a i ≤ 5 1 \le ai \le 5 1ai5

【输入样例1】
1
1
【输出样例1】
3
【输入样例2】
1
2
【输出样例2】
2
【输入样例3】
2
3 5
【输出样例3】
3
【原题链接】

https://www.acwing.com/problem/content/4806/


【题目分析】

签到题,枚举 + 模拟即可。

【复盘后的优化代码】✅
#include<bits/stdc++.h>

using namespace std;

int n, a, s;

int main() {

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

    int cnt = 0;
    for (int i = 1; i <= 5; i++) {
        if ((s + i) % (n + 1) != 1)
            cnt++;
    }
    cout << cnt << endl;

    return 0;
}
【周赛现场 AC 代码】
#include<bits/stdc++.h>

using namespace std;

const int N = 110;
int s[N], a[N], n;

int main() {

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

    int cnt = 0;
    for (int i = 1; i <= 5; i++) {
        if ((s[n] + i) % (n + 1) != 1)
            cnt++;
    }
    cout << cnt << endl;

    return 0;
}
【代码对比分析】

空间复杂度 进行了部分优化。



【题目B】构造矩阵

【题目描述】

给定一个 m m m n n n 列的 01 01 01 矩阵 B B B

请你构造一个 m m m n n n 列的 01 01 01 矩阵 A A A

矩阵的行编号为 1 ∼ m 1∼m 1m,列编号为 1 ∼ n 1∼n 1n

B i j B_{ij} Bij 表示矩阵 B B B 中第 i i i 行第 j j j 列的元素。

要求,构造的矩阵 A A A 满足,对于每一个 B i j B_{ij} Bij

  • 如果 B i j = 0 B_{ij}=0 Bij=0,则矩阵 A A A 中第 i i i 行的 n n n 个元素以及第 j j j 列的 m m m 个元素总共 n + m − 1 n+m−1 n+m1 个元素 必须 全都为 0 0 0
  • 如果 B i j = 1 B_{ij}=1 Bij=1,则矩阵 A A A 中第 i i i 行的 n n n 个元素以及第 j j j 列的 m m m 个元素总共 n + m − 1 n+m−1 n+m1 个元素 不能 全都为 0 0 0
【输入】

第一行包含两个整数 m , n m,n m,n

接下来 m m m 行,每行包含 n n n 个整数 0 0 0 1 1 1,其中第 i i i 行第 j j j 列的整数表示 B i j B_{ij} Bij

【输出】

如果不存在满足条件的矩阵 A A A,则输出一行 NO 即可。

否则,第一行输出 YES,接下来 m m m 行,每行输出 n n n 个整数,用来表示矩阵 A A A

如果答案不唯一,则输出任意合理答案均可。

【数据范围】

5 5 5 个测试点满足 1 ≤ m , n ≤ 5 1 \le m,n \le 5 1m,n5

所有测试点满足 1 ≤ m , n ≤ 100 1 \le m,n \le 100 1m,n100

【输入样例1】
2 2
1 0
0 0
【输出样例1】
NO
【输入样例2】
2 3
1 1 1
1 1 1
【输出样例2】
YES
1 1 1
1 1 1
【输入样例3】
2 3
0 1 0
1 1 1
【输出样例3】
YES
0 0 0
0 1 0
【原题链接】

https://www.acwing.com/problem/content/4807/


【题目分析】

周赛时看到数据范围较小,且暂时没有想到更好的方法,就直接模拟了(时间复杂度 O ( n 3 ) O(n^3) O(n3),思路详见代码注释),想着等周赛结束后,再复盘进行优化。没想到y总讲解时,也是直接用了 O ( n 3 ) O(n^3) O(n3) 的做法,弄巧成拙,2333。😂

这也告诉我在比赛时,如果没有太好的优化思路,先把当前的想法实现是最重要的,不要眼高手低。(除非当前思路无法 AC,否则不要死磕优化,毕竟比赛期间拿分最重要。优化可以等复盘时再仔细思考)

当然,本题硬要优化也不是不可以,但是会有特判,感觉实际写起来也没简化多少。

【周赛现场 AC 代码】
#include<bits/stdc++.h>

using namespace std;

const int N = 110;
int a[N][N], b[N][N], m, n;

// 把a的第i行和第j列置为0
void setV(int row, int col) {
    for (int i = 1; i <= n; ++i) a[row][i] = 0;
    for (int i = 1; i <= m; ++i) a[i][col] = 0;
}

// 根据b[i][j]的值,判断当前矩阵a是否满足条件
bool check(int row, int col) {
    // b[i][j] = 1,判断a中第i行和第j列是否存在1,有1返回true
    if (b[row][col]) {
        for (int i = 1; i <= n; ++i) {
            if (a[row][i] == 1)
                return true;
        }
        for (int i = 1; i <= m; ++i) {
            if (a[i][col] == 1) {
                return true;
            }
        }
        return false;
    }
    // b[i][j] = 0无需判断,因为a的第i行第j列在之前已经被置为0
    return true;
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入矩阵b,同时把矩阵a全部置为1
    cin >> m >> n;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> b[i][j];
            a[i][j] = 1;
        }
    }

    // 如果b[i][j]=0,就把a的第i行和第j列置为0
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (!b[i][j]) {
                setV(i, j);
            }
        }
    }

    // 判断矩阵a是否满足条件
    bool flag = true;
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (!check(i, j)) {
                flag = false;
                goto end;
            }
        }
    }

    // 输出结果
    end:
    if (flag) {
        cout << "YES" << endl;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                cout << a[i][j] << " ";
            }
            cout << endl;
        }
    } else {
        cout << "NO" << endl;
    }

    return 0;
}


【题目C】加减乘

【题目描述】

规定两种数字操作:

  • 加减操作,将数字加 1 1 1 或减 1 1 1,代价为 x x x
  • 乘法操作,将数字乘 2 2 2,代价为 y y y

每种操作的使用次数不限。

请你计算,通过上述操作,将 0 0 0 变为 n n n,所需花费的最小代价。

【输入】

共一行,包含三个整数 n , x , y n,x,y n,x,y

【输出】

一个整数,表示最小代价。

【数据范围】

5 5 5 个测试点满足 1 ≤ n ≤ 100 1 \le n \le 100 1n100
所有测试点满足 1 ≤ n ≤ 1 0 7 1\le n \le 10^7 1n107 1 ≤ x , y ≤ 1 0 9 1 \le x,y \le 10^9 1x,y109

【输入样例1】
8 1 1
【输出样例1】
4
【输入样例2】
8 1 10
【输出样例2】
8
【原题链接】

https://www.acwing.com/problem/content/description/4808/


【题目分析】

一眼DP,但是略有难度,思路如下(详解见y总视频讲解 视频链接 ):

  • 0 0 0 n n n 的过程转化为 n n n 0 0 0 的过程
  • 分析、推导出两个重要性质
    • 加法和减法不能连续出现
    • 加法不能连续出现
  • 有了上面两个性质,就可以推导状态转移方程了
    • n n n 为奇数时, d p [ i ] = min ⁡ ( d p [ i − 1 ] + x , d p [ ( i + 1 ) / 2 ] + x + y ) dp[i] = \min (dp[i - 1] + x, dp[(i + 1)/2] + x + y) dp[i]=min(dp[i1]+x,dp[(i+1)/2]+x+y)
    • n n n 为偶数时, d p [ i ] = min ⁡ ( d p [ i / 2 ] + y , d p [ i − 1 ] + x ) dp[i] = \min (dp[i/2] + y, dp[i-1] + x) dp[i]=min(dp[i/2]+y,dp[i1]+x)
    • 边界条件: n = 1 n = 1 n=1 时, d p [ 1 ] = x dp[1] = x dp[1]=x

附:

加法操作不能连续的证明

image-20230207111244210

状态转移方程:

image-20230207111338542

【复盘后的优化代码】✅
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;

const int N = 1e7 + 10;
LL dp[N];  // 内存计算:8Byte * 1e7 = 80MB < 256MB
int n, x, y;

int main() {

    cin >> n >> x >> y;

    // 求最小值,将dp数组置为一个较大值
    memset(dp, 0x3f, sizeof(dp));
    dp[1] = x;
    for (int i = 2; i <= n; i++) {
        if (i & 1)
            dp[i] = min(dp[i - 1] + x, dp[i + 1 >> 1] + x + y);
        else
            dp[i] = min(dp[i / 2] + y, dp[i - 1] + x);
    }
    cout << dp[n] << endl;

    return 0;
}
【周赛现场未 AC 代码】❌
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int x, y, n;
LL sum, dp[N];


int main() {
    cin >> n >> x >> y;

    dp[1] = x;
    for (int i = 2; i < N; ++i) {
        if (i & 1) {
            dp[i] = (LL) dp[i - 1] + x;
        } else {
            dp[i] = min((LL) dp[i - 1] + x, (LL) dp[i / 2] + y);
        }
    }

    cout << dp[n] << endl;
    return 0;
}
【代码对比总结】

对比代码,可以发现周赛现场的代码和 AC 代码就差临门一脚,但是经过y总分析后,两者其实天差地别。

因为自己比赛时,没有从数学层面推导出相关性质,不知道该如何证明,所以始终不知道如何处理 -1 的操作(从 0 0 0 n n n 的角度)

因为状态转移方程如果写成 d p ( i ) = min ⁡ ( d p ( i / 2 ) + y , d p ( i − 1 ) + x , d p ( i + 1 ) + x ) , i 为偶数 dp(i) = \min(dp(i/2)+y,dp(i-1)+x,dp(i+1)+x),i为偶数 dp(i)=min(dp(i/2)+y,dp(i1)+x,dp(i+1)+x),i为偶数

在推导 d p ( i ) dp(i) dp(i) 的时候用到了 d p ( i + 1 ) dp(i+1) dp(i+1),就会使程序陷入了死循环。

所以博主在周赛现场单纯去掉了 d p ( i + 1 ) dp(i+1) dp(i+1),保留了另外剩下两个,想碰碰运气。💦

但是事实证明,虽然这在 i i i 为偶数时是恰好正确的,但是在奇数时,就是完全错误的,因此始终不能 AC。

说明之后还是要多积累,多学习数学方面的证明,让代码有理可依。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值