Skip to content

2024CAS第一次月赛题解 #4

Description

@wellwei

难度一览

  • 简单题:ADL
  • 中等题:BGHIM
  • 难题:EFJK

A

题意:找到矩形的长和宽、利用最大公约数化简。

#include <stdio.h>
char s[520][520];
int n, m;

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%s", (s + i));	// s + i 就相当于s[i]

    int wid = 0, hei = 0, stx, sty; // 矩形宽度、高度以及矩阵开始坐标
    int flag = 0; // 标记是否找到起始点'x'

    // 查找第一个'x'的位置作为起始点
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (s[i][j] == 'x') {
                stx = i;
                sty = j;
                flag = 1;
                break;
            }
        }
        if (flag) break; // 如果找到'x',则退出循环
    }

    // 从起始点开始,向下计算高度
    for (int i = stx; i < n; i++) {
        if (s[i][sty] == 'x') ++hei;
        else break; // 如果遇到非'x'字符,则停止计算高度
    }

    // 从起始点开始,向右计算宽度
    for (int j = sty; j < m; j++) {
        if (s[stx][j] == 'x') ++wid;
        else break; // 如果遇到非'x'字符,则停止计算宽度
    }

    // 计算找到的矩形区域的宽和高的最大公约数,用于找到最小矩形
    int t = gcd(wid, hei);
    wid /= t;
    hei /= t;

    for (int i = 1; i <= hei; i++) {
        for (int j = 1; j <= wid; j++) {
            putchar('x');
        }
        puts(""); // 输出换行符
    }
    return 0;
}

B

题意:计算有多少个数,恰好等于集合中另外两个(不同的)数之和

方法一

用一个数组标记 $i$ 的数量,循环两数和与第一个数,判断另一个数是否存在。

int a[100010];
int num[100010];	//每个数的数量
int n, m;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        num[a[i]]++;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (a[j] > a[i]) continue;
            int k = a[i] - a[j];	//判断第二个数是否存在
            if (k == a[j]) {	//如果两数相同需要判断是否有两个
                if (num[k] > 1) {
                    ++ans;
                    break;
                }
            } else {
                if (num[k]) {	//如果第二个数存在那么答案++
                    ++ans;
                    break;
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}

方法二

// 先排序,再找前面有多少组数据满足
// 该题要注意:都是正整数,因此l++后,和一定增加,r--后,和一定减少
const int N = 100;
int a[N];
int n;
void solve()
{
	int ans = 0;// 答案
	int flag = n - 1;// 目标和的下标
	while (flag >= 2)// 一定要满足有三个数l, r, flag
	{
		int l = 0, r = flag - 1;
		while (l < r)
		{
			if (a[l] + a[r] == a[flag])
			{
				ans++;
				break;// 得出答案就退出,因为他并不需要计算有多少组满足 a[l] + a[r] = a[flag]
			}
			else if (a[l] + a[r] > a[flag])
				r--;
			else
				l++;
		}
		flag--;
	}
	cout << ans << endl;
}
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
	sort(a, a + n);
	solve();
	return 0;
}

C

签到题,按题意输出即可

#include<stdio.h>
int main()
{
	printf("Concentration Acceptance Software");
	return 0;
}

D

每个题单在当天 dyh 都会写一个题。
对于每个题单增长到最高的时候只有两种情况

  • ① 被清空后递增到 n 后又递减回来

  • ② 被清空后递减到 1 后又递增回来

    假设当前题单 i,前面有 x 个题单,后面有 y 个题单
    例举可以发现最大数量为 max(x,y)*2 个题。

#include <bits/stdc++.h>
using namespace std;
int n;
int main() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		// 对于当前题单i,计算其前面和后面的题单数量中的较大值
		// i表示当前题单的索引,n-i-1表示当前题单后面的题单数量
		int x = max(i, n - i - 1);
		// 1. 被清空后递增到n后又递减回来
		// 2. 被清空后递减到1后又递增回来
		// 在这两种情况下,最大数量都是前后题单数量的两倍
		cout << x * 2 << endl;
	}
	return 0;
}

E

//橙子蛋糕
#include<iostream>
using namespace std;

int main()
{
    long long n, k;
    scanf("%lld%lld",&n,&k);

    // 推断易得 k 与 n - k 的结果一样,故要判断补数n - k
    // 如果 k 大于 n 的一半,则计算 k 的补数,并赋值回 k
    if (k > n / 2)
        k = n - k; // 选择较小的 k 可以简化后续计算

    // 如果 k 等于 n 减 k,即刚好是对角,就可以直接输出 n
    if (k == n - k)
        printf("%lld",n);
    else
        // 正常的选取情况就是 n*k + 1
        printf("%lld",n * k + 1);

    return 0;
}

F

因为要求舍入后的最大值,所以只需要找到能舍入的最高位即可(从左往右第一个 $ >=5 $的数)

const int N = 2e5 + 10;
char s[N];
int n, t;
int main() {
    scanf("%d %d", &n, &t);
    scanf("%s", s);
    int pos;//记录小数点的位置
    for (int i = 0; i < n; i++) {
        if (s[i] == '.') {
            pos = i;
            break;
        }
    }
    int len = n; //记录保留字符串的长度
    for (int i = pos + 1; i < n; i++) {
        if (s[i] >= '5') { //从左往右找到第一个大于等于5的数字的位置开始四舍五入
            len = i;
            t--;
            i--;
            while (s[i] == '4' && t != 0) { //检查是否能继续四舍五入
                len = i;
                t--;
                i--;
            }
            if (s[i] == '.') { //检查是否需要进位到整数部分
                len = i;
                i--;
                while (s[i] == '9') { //处理如果进位到整数最后一位是9的情况
                    s[i] = '0';
                    i--;
                }
                if (i == -1) {
                    printf("1");//处理进位到最前面的情况(如99.5到100)
                }
                else s[i] += 1;
            }
            else s[i] += 1;
            break;
        }
    }
    for (int i = 0; i < len; i++) {
        printf("%c", s[i]);
    }
    return 0;
}

G

int t, n;
int a[200005], x;
int ans = -1000000;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        if (x + a[i] > 0)
            x += a[i];
        // 判断是否将当前整数加入当前的子序列中
        // 如果加入后子序列的和仍然为正,则加入
        else
            x = a[i]; //说明a[i]是负数,但记下这个负数的原因是防止所有元素都是负数
        //都是负数的话,答案理应是最大的那个负数
        ans= max(ans, x);
    }
    printf("%d\n", ans);
}

H

注意一个地方可以购买多次,如果位置 $i$ 的价格是 $ i $ 到 $ n $ 价格中最小的那么就尽可能多购买。

const int N=2e5+10;
int a[N],mi[N];

void solve() {
	int n;
    scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
	}
	mi[n] = 2e9; // 将最后一个元素初始化为一个很大的值,以便后续比较
	for (int i = n - 1; i >= 0; --i) {
		mi[i] = min(a[i], mi[i + 1]); // 从后往前找最小值
	}
	int ans = 0;
	int sum = 0;
	for (int i = 0; i < n; ++i) {
		++sum; // 每次循环增加计数
		if (a[i] == mi[i]) { // 如果当前元素是该位置后的最小值
			int t = sum / a[i]; // 计算可以“消耗”的块数
			ans += t; // 更新结果
			sum -= t * a[i]; // 减去所“消耗”的数量
		}
	}
    printf("%d\n", ans);
}
int main() {
	solve();
	return 0;
}

I

对于唐嘉富来说,最优的策略是对数组中最大的 x 个元素取反。因此,为了最小化唐嘉富造成的损害,孙洁龙应该总是移除一些最大的元素。
为了解决这个问题,我们可以对数组进行排序,并遍历 i(0≤i≤k),其中 i 是孙洁龙移除的元素数量。
对于每个 i,我们知道孙洁龙将移除数组中最大的 i 个元素,然后唐嘉富会对剩下的 x 个最大元素取反。
因此,我们可以利用前缀和快速计算出最终的和。由于排序的存在,时间复杂度为 O(nlogn)。

#include <bits/stdc++.h>
using namespace std;
int A[2000010];
void solve() {
	int n, k, x;
	cin >> n >> k >> x;
	for (int i = 1; i <= n; i++)
		cin >> A[i];
	sort(A + 1, A + n + 1, greater<int>()); // 这里使用了C++的排序函数sort,C语言可使用qsort函数或者手搓快速排序。

	for (int i = 1; i <= n; i++)
		A[i] += A[i - 1];// 计算前缀和,A[i]现在表示前i个元素的和

	int ans = -1e9; // 初始化答案为一个非常小的数(负无穷大的近似值)
	// 遍历孙洁龙可能移除的元素数量 i( 0 到 k )
	for (int i = 0; i <= k; i++)
		// 更新答案,取当前答案和( A[n](总和)- 2 * A[min(i + x, n)](
		//移除i个后唐嘉富取反x个的和的两倍,因为取反相当于减去两倍)+ A[i](孙洁龙移除i个的和))的较大值
		ans = max(ans, A[n] - 2 * A[min(i + x, n)] + A[i]);

    printf("%d\n", ans);
}
int main() {
	int tc;
    scanf("%d", &tc);
	while (tc--)
		solve();
}

J

通过画图易得:

1 x n 答案就是 n,m 中得较大者

2 x n分三种情况:

  • 第一种即 m 可以被 2 整除但不可以被 4 整除,那么最优方案即 n + m
  • 第二种即 m 即可被 2 整除又可被 4 整除,最优方案即放满其中一排即 max(n, m)
  • 第三种即 m 为奇数,最优方案为max(n, m) + 1

其他情况易得数量为 (n x m + 1) / 2

void solve() {
    int n, m;
    cin >> n >> m;
    if (n == 1 || m == 1)
        printf("%d\n", max(n, m));
    else if (n == 2 || m == 2) {
        if (n == 2 && m % 2 == 0 && m % 4 != 0)
            printf("%d\n", n + m);

        else if (m == 2 && n % 2 == 0 && n % 4 != 0)
            printf("%d\n", n + m);

        else if (max(n, m) % 2 == 0)
            printf("%d\n", max(n, m));

        else printf("%d\n", max(n, m) + 1);
    }
    else printf("%d\n", (n * m + 1) / 2);
}

int main() {
    int t = 1;
    while (t--)
        solve();
    return 0;
}

K

只需要先将每一个学妹以结束时间排一次序,得到一个排序后的结构体数组,
存储第一个学妹的结束时间,然后以结束时间为准,
跟下一个学妹开始时间比较,不小于第一个学妹的结束时间就证明不冲突,然后计数,
再记录下这个学妹的结束时间,以此类推,最后输出总次数就可以了。
(因为将结束时间排序,所以只要确定开始时间没有冲突,那整段时间就不会又任何冲突)

#include<stdio.h>
#include<algorithm>//算法库(包含sort函数)
struct st { //(定义一个结构体数组,分别储存开始时间和结束时间)
	int a;//(开始时间)
	int b;//(结束时间)
} x[1000010];

bool cmp(st x, st y) { //(不管开始时间,直接按照结束时间排序)
	return x.b < y.b;
}

int main() {
	int n, ans = 1, cnt;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &x[i].a, &x[i].b);
	}
	std::sort(x + 1, x + n + 1, cmp); //(排序)亦可手写自定义关键字快速排序
	cnt = x[1].b; //(无脑记录第一个值)
	for (int i = 2; i <= n; i++) {
		if (x[i].a >= cnt) { //(找到符合要求的,记录)
			ans++;//(计数)
			cnt = x[i].b;
		}
	}

	printf("%d\n",ans);
	return 0;
}

L

由题意可得,合法得字符串一定是由 iguangguangshen 组合而成得,所以我们只需要判断整个字符串是否只由这两种字符串组成。

#include <stdio.h>
#include <string.h>

char s[500010];

void solve() {
    scanf("%s", s);
    int f = 0;
    int i;
    char a[7];
    char b[10];
    int len = strlen(s);
    for (i = 0; i < len; ) {

        f = 0;
        strncpy(a, s + i, 6);
        a[6] = '\0';

        if (strcmp(a, "iguang") == 0) {
            f = 6;
        }

        strncpy(b, s + i, 9);
        b[9] = '\0';

        if (strcmp(b, "guangshen") == 0) {
            f = 9;
        }

        if (f == 9) i += 9;
        else if (f == 6) i += 6;
        else break;
    }
    if (f == 0 && i < len) printf("No\n");
    else printf("Yes\n");
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}

M

能最多把 k 个 0 变成 1 的话,并且要求最多连续 1 的长度,那我们就可以直接遍历这
个数组,找到符合题目条件的长度,然后一直取到最大值即可,如果暴力遍历的
话肯定是会超时的,n 最大是1e5,k 最大是1e5,O(n * k)的复杂度是不行的,
本题可以先进行前缀和再采取双指针的解决方案复杂度为 O(n)。
因为数组中只有 0 或 1,因此前缀和的意思是方便找出区间的 1 的总个数,在遍历
的时候,判断条件必须是 $ (s[r] - s[l - 1] + k >= r - l + 1 ) $ ,r指针才会
往右加,不然就算 l 指针往右加。这个判断是意思是[l,r]区间的 1 的个数加上可
以把 0 变成 1 的个数,要大于这个区间长度,因为这个 k 是最多,所以可能会超
出.

#include <iostream>
using namespace std;
int a[100520],s[100520];
int main() {
	int n, k;
    scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);

	for (int i = 1; i <= n; i++) {
		s[i] = s[i - 1] + a[i];
	}
	int ans = -0x3f3f3f3f;
	int l = 1, r = 1;
	while (r <= n) {
		if (s[r] - s[l - 1] + k >= r - l + 1) {
			ans = max(ans, r - l + 1);
			r++;
		} else {
			l++;
		}
	}
    printf("%d\n", ans);
	return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    solutionAlgorithmic Problem Solution

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions