NEUQ-ACM预备队训练-招新赛补题

前言

作为一名初学者,学艺不精,解题办法基础粗糙。

招新赛补题前六道并未涉及算法部分,使用基础方法也可以正常解出,这也是本次作业限定前6题的用意吧

题目集:

7-1 Win

题目

在这里插入图片描述

代码块

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string a;
    cin>>a;
    if(a=="NEU")
    {
        printf("Win");
    }
    else if(a=="THU")
    {
        printf("Lose");
    }
    else printf("?");
    return 0;
}

解题思路

借助字符串的比较方式简单判断即可,不多说。

7-2 比大小

题目

在这里插入图片描述
在这里插入图片描述

代码块

#include<iostream>
#include<string>
using namespace std;
int main()
{
    int n;cin>>n;
    for(int i=0;i<n;i++)
    {
        string a,b;
        cin>>a>>b;
        if(a>b)printf(">");
            else if(a==b)printf("=");
        else printf("<");
    }
    return 0;
} 

解题思路

仔细阅读以后,可以发现,田所浩二先生的比较大小办法是从高位到低位依次比较数字的大小,若某数字高位较大,则不再比较后续数字,直接认为该数字大。可以发现,这恰恰是字符串的比较模式。

因此,借助字符串的比较模式,将数字作为字符串输入,从左往右依次遵循ASCII码值的大小比较,大者大,小者小,若相等,则移至下一位继续比较,直到分出大小或者全部相等。

7-3 矩阵乘法

题目

在这里插入图片描述

在这里插入图片描述

代码块

#include<bits/stdc++.h>
using namespace std;
int main() 
{
	int N,P,M;
	cin>>N>>P>>M;
	int a[N][P],b[P][M];
	for(int i=0; i<N; i++) {
		for(int j=0; j<P; j++) {
			cin>>a[i][j];
		}
	}
	for(int i=0; i<P; i++) 
    {
		for(int j=0; j<M; j++) {
			cin>>b[i][j];
		}
	}
	int c[N][M];
	memset(c,0,sizeof(c));
	for(int i=0;i<N;i++){
		for(int j=0;j<M;j++){
			for(int k=0;k<P;k++){
				c[i][j]+=a[i][k]*b[k][j];
			}
			cout<<c[i][j]<<" ";
		}
		if(i!=N-1)	cout<<endl;
	}
	return 0;
}

7-4 疯狂星期四

题目

在这里插入图片描述

代码块

#include<iostream>
using namespace std;
int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int b[13] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 };
int c[7] = { 0,1,2,3,4,5,6 };
int main()
{
    int m, d, y;
    cin >> m >> d >> y;
    int simple = 0, special = 0;
    int date = 0;
    if (y >2023)
    {
        for (int i = 2023; i < y; i++)
        {
            if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))special++;
            else simple++;
        }
    }
    else if (y < 2023)
    {
        for (int i = y; i < 2023; i++)
        {
            if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))special--;
            else simple--;
        }
    }
    //记录了年就能算出当年的第一天是星期几
    date += (simple + special * 2)%7;
    if (date < 0)date += 7;
   //date记录了当年的第一天的周数
    int day = 0;
    if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
    {
        for (int i = 1; i < m; i++)day += b[i];
    }
    else
    {
        for (int i = 1; i < m; i++)day += a[i];
    }
    //记录天数
    day += d;
    day %= 7;
    date = (date + day) % 7;
    if (date == 0)printf("6");
    else
    printf("%d", c[date-1]);
    return 0;
}

解题思路

这里笔者通过观察日历发现,如果当年的第一天是星期一,那么若当年为普通年,下一年第一天为星期二,若当年为闰年,下一年的第一年为星期三,也就是每过一个普通年退后一天,每过一个闰年退后两天。

并且发现了2023年的第一天刚刚好是周日,因此并没有使用题给数据而是使用了2023年1月1日这个数据,极大简化了步骤。

7-5 排列

题目

在这里插入图片描述

代码块

#include<iostream>
using namespace std;
int main()
{
	int n, j;
	cin >> n >> j;
	int a[n+1];
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	
	int gap = j - 1;//gap是我每次涂色真正能影响到的色块,因为那个最大
	                //值色块我始终无法改变它,却在每个j中都需要包含它
	cout << (n-1+gap)/gap;
	return 0;
}

解题思路

为方便,我们将问题转化为涂色问题,假定那个最大的值就是我们所需要涂的正确颜色,其他非该值的都是不正确的颜色,我们需要利用给定的间隔j来确定我们一次性可以涂多少个色块.

笔者看似直接使用了简单的数学公式,但在背后笔者做出了许多思考,也在不断调试中找到了这个正确的思路首先,我们读题可以发现,如果一个比较大的色块中没有我们需要的最大值,那么对这个色块进行涂色,把其中最大的值赋给其他色块是没有意义的,比如 1 3 5,即便全部涂上5的颜色,最后也等于没有进行这步操作,只是白白增加了一次涂色次数。所以我们确定了:如果要求最短的操作次数的话,我们应该在每一次涂色操作中,要至少有一个色块的值是我们所需要的最大值。

于是,笔者开始尝试

int flag=0
for(int i=1;i<=n;i++)
{
  if(a[i]==n)
  {
    flag=i;
    break;
  }
}

借助这种简单的遍历方式,找到那个最大值在哪里,之后从最大值向左、向右依次涂色(即每种涂色方式都以这个flag标记的位置为起点)。后来笔者很快发现,这并不是一个AC代码,只能获得很少的测试点分,思考后发现,并不是以最大值为起点,依次向左向右涂色就是最优解,这样做只能保证没有像上述 135涂色变成555这样的无用操作。什么意思呢?意思是:

//以样例为例
1 6 4 2 3 5
我先进行向左涂色,发现只需要涂一次
6 6 4 2 3 5
再向右涂色,每次仅能涂两格(gap与j)
6 6 6 6 3 5
6 6 6 6 6 6 
看起来操作是一样的,可是细心的读者应该发现了,我在第一步操作的时候
很明显浪费了一个涂色格,如果我们不是6个数字,而是五个数字,那么
1 5 4 2 3
5 5 4 2 3
5 5 5 5 3
5 5 5 5 5
很明显,我的操作次数变成了3
可是,我却可以用这样的办法,将操作次数减为2
1 5 4 2 3
5 5 5 2 3
5 5 5 5 5
这也就是为什么不是以最大值为起点终点就可以是最优解。

后来笔者开始思考,直到笔者意识到一件事:

1 5 3 2 4
5 5 5 2 4
这样的涂色办法,与
5 1 3 2 4
5 5 5 2 4
并没有任何区别,在经过一次涂色后,第一种排列和第二种排列完全是
意义相同的
也就是说,假定我们的最大值中间
1 2 3 7 4 5 6
我们最优的涂法(不唯一,但这样可行)
1 2 7 7 7 5 6//第一步
7 7 7 7 7 5 6//第二步
7 7 7 7 7 7 7
在整个操作流程上,我们把第二步逆向回去,我们不要认为最大值在第四位
直接认为最大值在第一位
7 1 2 3 4 5 6
7 7 7 3 4 5 6
7 7 7 7 7 7 7
发现和最优解并无区别

至此,终于发现题解中那n-1/k-1向上取整的含义

本质上,为了达到最优解,我们需要做到两个要求

  1. 每次涂色,我们都能有效涂色(j包含的那几个色块里有最大的色块)

  2. 我们至少应该保证有一边没有被因为越界而浪费格子

    那我们怎么完成呢?其实就是抽象化地把位于中间的最大值抽象到最左边或最右边

    细想一下,为了保证有一边没有因为越界而浪费格子,是不是说,在我们某次涂色时

    会刚好把第一个数或最后一个数涂成最大值呢?那是不是就等于最大值在第一个或最后一个数,然后从它开始依次涂色呢?

7-6 小步点

题目

在这里插入图片描述

代码块

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
    int a[5][2] = { 0 };
    for (int i = 0; i < 5; i++)
        for (int j = 0; j < 2; j++)cin >> a[i][j];//到此输入数据 简单环节结束
    //我们试着写一个120长度的数组 记录每一种移动的方式
    double b[120] = { 0 };
    int z = 0;
    //循环变量 i j k x y
    for (int i = 0; i < 5; i++)//i
    {
        
        for (int j = 0; j < 5; j++)//j
        {
            if (j == i)continue;          
            for (int o = 0; o < 6; o++)//从0到5这六个情况 全部加上0-1的距离
            {
                b[z+o] += sqrt(pow(a[i][0] - a[j][0], 2) + pow(a[i][1] - a[j][1], 2));
            }
                for (int k = 0; k < 5; k++)
                {
                    if (k == i || k == j)continue; 
                    for (int o = 0; o < 2; o++)//从1-2的距离加给后续两个3-4 4-3
                    {
                        b[z+o] += sqrt(pow(a[k][0] - a[j][0], 2) + pow(a[k][1] - a[j][1], 2));
                    }
                        for (int x = 0; x < 5; x++)
                        {
                            if (x == k || x == j || x == i)continue;
                            b[z] += sqrt(pow(a[x][0] - a[k][0], 2) + pow(a[x][1] - a[k][1], 2));
                                for (int y = 0; y < 5; y++)
                                {
                                    if (y == x || y == k || y == j || y == i)continue;
                                        b[z] += sqrt(pow(a[y][0] - a[x][0], 2) + pow(a[y][1] - a[x][1],2));
                                        z++;

                                }
                        }
                }
        }
    }
        sort(b, b + 120);
        printf("%.3lf", b[0]);
        return 0;
}

解题思路及关键步骤

感谢学长手下留情,这道题并没有一定需要使用算法,利用简单的五层循环即可

实际上,五个点,也就是五的阶乘 一共120种情况,可是在做题过程中,却不断出现

2.828的答案。显然,这是二倍根号二,表面有一定的步骤缺失。

后来发现,如果我们就简单的以如下的方式考虑:

在这里插入图片描述

却很容易发现数据缺失的地方,如果我们去掉那两个以o为循环体的变量

在第一次进入y也就是第五个点的情况下 0 1 2 3 4这五个点四个距离都可以正常加在b[0]中去,然而,当这个情况结束后,重新进行的循环是0 1 2 4 3 ,只有2-4-3这两段距离被存在了b[1]中,0-1-2这两段距离并没有写入b[1]才导致大量的最小值二倍根号二出现

笔者自行多次查看后,发现,数据缺失只会在第一个点到第三个点之间,因此需要把第一个到第二个点如上图那样赋给六个数组元素,同时分别把第二个点到第三个点的距离依次加到两个数组元素中,就解决了问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值