华北理工大学第一届ACM校赛题解

忙里偷闲当了次出题人,记录一下这次经历。先晒一下我优秀的学妹学弟们~ 参赛基本都是大一大二的。
题解ppt:校赛题解

在这里插入图片描述

A 参赛人数


Description

alpha想知道本次比赛的实际参赛人数。他想了个办法,看一下签到题有多少人ac,这不就行了嘛!
但是他怕签到题不简单,这样就无法统计到准确的人数。他为此很苦恼。
如果你觉得这道题很简单,也不想让alpha继续困扰下去,就输出”tjdl!”,去告诉alpha,这道题“太简单了!”

Input

无输入

Output

tjdl!

Sample Input

 

Sample Output

tjdl!

赛前阎老师一再嘱咐我题目不要出太难,于是诞生了这道题。
这题不会的同学们是认真的吗。。

#include <stdio.h>
int main()
{
    printf("tjdl!\n");
    return 0;
}

B 积木游戏


Description

beta喜欢玩积木游戏,游戏的规则是这样的:
最初有n个积木堆,各堆有自己的初始高度。游戏通过有限次数的移动,一次移动一个积木,最终使所有堆的高度相同。
beta想知道要达到目标需要的最少移动次数,他请来了精通程序设计的你来帮他算一算。

Input

输入包含多组数据,以0作为输入结束。
每组数据包含两行:
第一行为一个正整数n,表示积木堆的个数
第二行为n个正整数,h1 h2 … hn ,为每堆初始的积木个数。

数据规模约定:
1≤n≤50,1≤hi≤100

Output

对于每一组输入数据,输出将全部堆变为相同高度最少的移动次数。
在每组输出结果之间输出一个空行。

Sample Input

6
5 2 4 1 7 5
2
1 3
0

Sample Output

5
1

More Info

在第一组样例输入中:
第1堆积木拿1个到第2堆;
第5堆积木拿1个到第2堆;
第5堆积木拿1个到第4堆;
第5堆积木拿1个到第4堆;
第6堆积木拿1个到第4堆;
即最少需5次操作。

在第二组样例输入中:
第2堆积木拿1个到第1堆;
即最少需1次操作。

送分题
题目要求移动积木使高度相同。
先求出所有高度平均值avg,再遍历全部高度:如果当前高度h>avg,则累加差值进答案。

#include <bits/stdc++.h>
using namespace std;
int a[55];
 
int main()
{
    int n, sum, avg;    
    while(scanf("%d", &n) && n) {
        sum = avg = 0;
        for(int i = 0; i < n; i++){
            scanf("%d", &a[i]);
            sum += a[i];
        }
        avg = sum / n;
        
        sum = 0;
        for(int i = 0; i < n; i++)
            if(a[i] > avg)
                sum += (a[i]-avg);
        printf("%d\n", sum);
    }
    return 0;
}

C 矩阵求和


Description

我们定义这样n*n的矩阵:
1/1

1/1 1/2
1/2 1/1

1/1 1/2 1/3
1/2 1/1 1/2
1/3 1/2 1/1

相信你已经发现规律了:此类矩阵都是主对角线为1的对称矩阵,且主对角线两边元素的分母递增。
对于一个n*n的矩阵,请求出这个矩阵各元素的和。

Input

输入包含多组测试数据。每行给定整数n(n<50000),表示矩阵的阶数为n。
当n=0时,输入结束。

Output

对于每组输入数据,输出矩阵各元素的和,结果保留2位小数。

Sample Input

1
2
3
4
0

Sample Output

1.00
3.00
5.67
8.83

送分题2
题目要求计算一个简单对称矩阵的元素和。
找规律即可。

#include <stdio.h>
int main()
{
    int n;
    while(scanf("%d", &n) && n) {
        double sum = 0.0;
        for(int i = 2; i <= n; i ++)
            sum += 1.0 / i * (n-i+1) * 2;
        printf("%.2f\n", sum + n);
    }
    return 0;
}

D EOF


Description

Rio一直觉得程序设计里有些缩写很酷。
比如End Of File可以缩写成EOF,就比eof酷。
他经过研究,发现这些各首字母大写形式的组合,其实都很酷。

Input

输入的第一行是一个整数T,表示一共有T组测试数据。
每组数据占一行,包含不多于20个单词,每两个单词之间由空格隔开。

规定:
每个单词长度不超过20,单词仅包含大、小写字母。

Output

每组测试数据输出其相应的缩写形式,每组输出占一行。

Sample Input

2
End Of File
input output system

Sample Output

EOF
IOS

没有字符串如何ACM?

新手可能不太好处理这种一行数字,一行字符串的输入内容。
C语言处理时往往通过 getchar() 吃掉数字后面的回车,或者用scanf(“%d\n”, &T)来接收数字。

如果忘记65、97、32(差值)这些常用的ascii码值,可以直接使用’a’、’A’、’A’-’a’进行计算。

此外,gets是不被建议使用的危险函数,在PAT等考试中已被禁止使用,但在蓝桥杯、ACM等竞赛中仍可使用。

C版本:

#include <bits/stdc++.h>
using namespace std;
char ss[250];
int main()
{
    int T;  
    scanf("%d", &T);
    getchar();
    while(T --)
    {
        gets(ss);
        for (int i = 0; ss[i]; i ++) {
            if (!i || ss[i-1] == ' ') {
                if (ss[i] >= 'a')
                    ss[i] -= 32;
                printf("%c", ss[i]);
            }
        }
        puts("");
    }
    return 0;
}

C++版本:

#include <bits/stdc++.h>
using namespace std;
 
int main()
{
    int T;  
	string str, temp;
    cin >> T;
    cin.ignore();
    while(T --)
    {
        getline(cin, str);
        stringstream ss(str);
 
        while (ss >> temp) {
            if (temp[0] >= 'a') temp[0] -= 32;
            cout << temp[0];
        }
        cout << endl;
    }
    return 0;
}

E 整数分解


Description

把一个正整数N分解成3个各不相同的正整数之和,并且要求每个正整数都不包含数字2和4,一共有多少种不同的分解方法?

注:不包含2和4指整数的各个位上的数都不为2且不为4。

Input

第一行一个整数T,表示输入有T组。
对于每一组输入数据,输入为一个正整数N。(3 < N < 2020)

Output

对每组输入数据,输出一个正整数,表示分解方法。

Sample Input

3
8
9
12

Sample Output

0
1
2

More Info

对样例的解释:
8没有满足要求的分解。
9分解为:1、3、5。
12分解为:1、3、8或1、5、6。

这题不是去年蓝桥杯填空原题么。。很难吗。。众大佬数论都用上了??

本题改编自2019蓝桥杯B组省赛填空题第四题。题目要求把一个正整数分解成3个各不相同的正整数之和,并且要求每个正整数都不包含数字2和4

如果使用如下形式的三层循环暴力搜索,时间达到2000^3导致超时,因此需要一些优化。

for (int i = 1; i < N; i ++)
        for (int j = i+1; j < N; j ++)
            for (int k = j+1; k < N; k ++)
                if (i + j + k == N && Check(i) && Check(j)&& Check(k))

优化可以是:在已经确定两个数i、j后,第三个数用N-i-j表示则可以节省一层循环。

#include <stdio.h>
using namespace std;
 
//检查数字num是否包含2或4
bool Check(int num)
{
    while(num)
    {
        if (num % 10 == 2 || num % 10 == 4)
            return false;
        num /= 10;
    }
    return true;
}
 
int main()
{
    int T, N;
    scanf("%d", &T);
    while (T --){
        scanf("%d", &N);
        int ans = 0;
        for (int i = 1; i < N; ++i)
            if (Check(i))
                for (int j = i + 1; N - i - j > j; ++j)
                    if (Check(j) && Check(N - i - j))
                        ans++;
        printf("%d\n", ans);
    }
    return 0;
}

F 围棋程序


Description

小A喜欢围棋,这天他心血来潮想要设计一个围棋小游戏,但是一个基本的问题是:该如何计算闭合线段围成的面积呢?
比如在下面这个5*5的由“0 1”组成棋盘中,由“1”围出来的“0”的面积是:3。

0 0 0 0 0
0 1 1 1 1
0 1 0 1 0
1 0 0 1 1
1 1 1 1 0

规定:面积的计算方法是统计“1”所围成的闭合曲线中水平线和垂直线交点的数目,特别地,在边上的0不被计算,如第3行第5列的“0”不算作面积。

Input

一个10*10的棋盘矩阵,棋盘中每个点由0、1组成。

Output

由题意输出1围成的0的面积。

Sample Input

0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 1
0 0 0 1 1 1 0 0 0 0
0 0 0 1 0 0 1 1 1 0
0 0 0 0 1 1 0 0 0 1
0 0 0 0 0 1 0 0 1 0
0 0 0 1 1 0 0 1 0 0
0 0 0 1 0 0 0 1 0 0
0 0 0 0 1 1 1 0 0 0
0 0 0 0 1 1 1 0 0 0

Sample Output

12

搜索。
直接截图题解ppt
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int maxn = 15;
int a[maxn][maxn];
int Next[4][2] = {0,1,0,-1,1,0,-1,0};
 
bool Check(int x ,int y) {
    return x >= 1 && y >= 1 && x <= 10 && y <= 10 && !a[x][y];
}
 
void dfs(int x, int y)
{
    a[x][y] = 2;
    for (int k = 0; k < 4; k ++) {
        int nx = x + Next[k][0];
        int ny = y + Next[k][1];
        if (Check(nx, ny))
            dfs(nx, ny);
    }
}
int main() {
    for (int i = 1; i <= 10; i ++)
        for (int j = 1; j <= 10; j ++)
            cin >> a[i][j];
    // 从边界的0开始搜索
    for (int i = 1; i <= 10; i ++) {
        if(!a[i][1]) dfs(i, 1);
        if(!a[i][10]) dfs(i, 10);
        if(!a[1][i]) dfs(1, i);
        if(!a[10][i]) dfs(10, i);
    }
    int ans = 0;
    for (int i = 1; i <= 10; i ++)
        for (int j = 1; j <= 10; j ++)
            if(!a[i][j]) ans++;
    cout << ans << endl;
    return 0;
}

G 搬家


Description

要说我校的搬家史那可有的说的了。就alpha来说,他这个暑假刚经历一次搬寝室。
alpha寝室里的东西有n件,但搬东西真的是件很累的事,他只准备随便搬2m件就行。
alpha每趟左手、右手分别拎一件物品。
凭借天资聪颖,他迅速地发现了:每搬一趟,他的疲劳度与左、右手的物品的重量差的平方成正比。例如他左手拿重量为3的物品,右手拿重量为6的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.
他想知道他搬完这2
m件物品后疲劳度最低是多少?

Input

每组输入数据有两行。
第一行有两个数n,m (2*m ≤ n < 2000)。
第二行有n个整数分别表示n件物品的重量(重量是一个小于215的正整数)。
0 0表示输入结束。

Output

对于每组输入数据,输出一个正整数,表示他最少的疲劳度,每组输出占一行。

Sample Input

5 1
18467 6334 26500 19169 15724 
7 1
29358 26962 24464 5705 28145 23281 16827 
0 0

Sample Output

492804
1399489

出之前确实想过dp的题有爆0的可能,然后真的爆0了。

本题选自hdu1421,题目要求从n件物品中选k对,要求每对差平方之和最小。
基于贪心的思想,相邻的数差值肯定比不相邻的小,所以要先排序。

dp思想:dp[i][j]表示从i个数中取j对物品时的最小值
取第i个物品: dp[i][j]=dp[i-2][j-1]+(v[i]-v[i-1])*(v[i]-v[i-1])
不取第i个物品: dp[i][j]=dp[i-1][j]
状态转移方程:
dp[i][j]=min{dp[i-2][j-1]+(v[i]-v[i-1])*(v[i]-v[i-1]),dp[i-1][j]

#include<bits/stdc++.h>
using namespace std;
 
const int maxn = 2050;
const int INF = 0x3f3f3f3f;
int dp[maxn][maxn];
int a[maxn];
 
int main()
{
    int n, k;
    while (cin >> n >> k && n)
    {
        for (int i = 1; i <=n; i++)
            cin >> a[i];
        sort(a+1, a+n+1);
 
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= k; j++)
                dp[i][j] = INF; 
        for (int i = 2; i <= n; i++)
            for (int j = 1; 2*j<= i ; j++)
                dp[i][j] = min(dp[i - 2][j - 1] + (a[i] - a[i - 1])*(a[i] - a[i - 1]), dp[i - 1][j]);
        cout << dp[n][k]<<endl;
    }
    return 0;
}

H 社团活动


Description

bbq的工作是管理学校的社团活动,具体来说是为每个社团活动分配教室。要把有限的教室合理安排给这些社团,是不容易的。
每个社团活动用k, t1, t2来表示:该社团活动在第t1天~第t2天内需要k个教室(包括t1,t2)。
bbq总是按社团活动申请的先后顺序分配教室,如果某一天剩余的教室数量不够满足某社团的要求,则停止教室的分配。bbq需要告知该社团,他们的该次社团活动无法进行。

Input

每组输入数据第一行包括两个正整数n、m,为总天数和社团活动的总数量。
第二行包含n个正整数,其中第i个数为ri,表示第i天空教室的数量。
接下来有m行,每行为一个社团活动的信息,包含三个正整数k, t1, t2。(k, t1, t2如题目描述)。

规定:
天数与社团活动编号均用从1开始的整数编号。
1≤n, m≤106,0≤ri≤109,0≤k≤109 ,1≤t1≤t2≤n。

Output

如果所有社团的申请均可满足,则输出0。

否则输出一个正整数,为需要bbq告知的活动无法进行的社团活动编号。

Sample Input

4 3
2 5 4 3
2 1 3
3 2 4
4 2 4

Sample Output

2

其实本来数据是卡线段树的,为了对背模版的同学一点奖励,我特意把卡常的数据删了,然后就真的被大一学妹用线段树A出来了,厉害呀。

题目大意:给n个数,做区间修改(减法),问第一个为负的是哪一位。
问题具有单调性(第k个活动无法满足,k或k前面的活动一定无法满足)。
所以二分答案即可。check函数的处理上用了查分数组,对于区间[l,r],要修改的值为d,则令sum[l]-=d,令sum[r+1]+=d即可。

此外,用线段树维护最小值是没问题的。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+50;
using ll = long long;
int n,m;
ll a[N],room[N],sum[N],t1[N],t2[N];
 
bool check(int mid)
{
    memset(sum,0,sizeof(sum));
    for (int i=1;i<=mid;i++)
    {
        sum[t1[i]]-=room[i];
        sum[t2[i]+1]+=room[i];
    }
 
    int cnt=0;
    for (int i=1;i<=m;i++)
    {
        cnt+=sum[i];
        if (a[i]+cnt < 0) return 0;
    }
    return 1;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    for (int i = 1; i <= m; i++)
        scanf("%lld%lld%lld", &room[i],&t1[i],&t2[i]);
    int l=1, r=m+1;
    while(l < r)
    {
        int mid = (l + r) >> 1;
        if (check(mid)) l = mid+1;
        else r = mid;
    }
    if (l == m+1) {printf("0\n");return 0;}
    printf("%d\n",l);
    return 0;
}

最后,祝我校ACM事业蒸蒸日上、学弟学妹明年再创佳绩!

发布了673 篇原创文章 · 获赞 644 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/zhaohaibo_/article/details/102758955