2021级HAUT新生周赛(寒假专场)题解

赛前题目难度估计:

简单题:F、H、K
中等题:A、I、D、J、C
困难题:G、E、B

希望这场能有不少人打,大伙也能做出不少题叭

题目解析(页面右上方有目录):

A.小C刷跑

这题主要的思考点在于怎样选取2号点的打点位置

我们可以将1 2 3号点设为A B C,设2号点的打卡位置的点设为F,那为了跑步距离最短,那么跑直线距离,那么跑步距离L=AF+FC-R(在R范围内即可打卡,那么打卡3号点只要到打卡范围即可)

而在式子中,AF+FC是F点到两定点距离之和,这会让我们想到椭圆(椭圆上到两定点之和相等),AC两点为定点,那么我们可以知道这个椭圆的a值是确定的,那我们只需要让椭圆的b值最小即可
在这里插入图片描述
在上图中,我们可以知道,该椭圆的b值,其实就是OF的距离,要b值最小,那么F点应该取在(x,y-R)的位置,确定了F点,那这题直接用式子算出AF+FC-R即是正确答案了!

不过,这题还需考虑另一个特殊情况:y比R小。
在这里插入图片描述
在这种情况下,直接从1号点跑到3号点的过程中就已经把2号点打上了,这种情况的距离是L=2*x-R

这样,这题就算彻底完成了!

上代码:

#include<stdio.h>
int main()
{
	long long x,y,R;   
	//这里用long long是因为后面计算平方时会超出int的存储范围,这直接用double也行
	double ans;
	scanf("%lld%lld%lld",&x,&y,&R);
	if(R>=y)
	{
		printf("%.2f",(double)(2*x-R));
		return 0;
	}
	ans=2*sqrt(x*x+(y-R)*(y-R))-R;
	printf("%.2f",ans);
	return 0;
}

B.小C爱编程

这题是一道背包题 (刚发现这题与昨天郑轻的10星题⼤嘴猫玩卡牌是差不多的)

也许这题会有同学用贪心写,即先将作业时间按大到小排序,然后再依次将作业分给左右手中目前写作业耗时少的那只手,最后求出答案。
这样的方法只能求出近似最优解,并不是完全正确的,这里提供一个错误样例:

6
105 88 118 36 70 77

对于这个数据,那种方法是分为118、77、36;105、88、70两组,算出答案为263
但正确答案应当分为105、77、70;118、88、36两组,答案为252

对于这题,我们可以用背包统计出左手分配到不同作业耗时的所有情况,然后再用写作业的总时长减去当前左手当前情况的时间,即可得到右手写作业的时间。对于每一种情况,对两只手取最大值,计算最大值最小即可。

那么,最重要的还是怎么用背包来表示出左手分配到作业的所有情况。

我们用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前i份作业中,有没有组成j分钟的作业组合,有则是1,没有则是0

初始情况是 d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1 ,没有作业的情况下,耗时是0分钟的可以实现的

后面要考虑怎么状态转移了,这里就直接把转移的式子给出来:

dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=sum;j++)  //sum指的是所有作业所需的总时间
{
	if(dp[i-1][j]==1) //表示前i-1份作业,存在可以组成j分钟的情况
	{
		dp[i][j]=1;  
		//如果当前这份作业我们不用左手写,即代表前i份,也可以组成j分钟的情况
		dp[i][j+a[i]]=1;
		//如果当前这一份作业用左手写,那么代表前i份作业,可以组成j+a[i]分钟的这种情况
		//(a[i]是当前这份作业所用的时间)
	}
}

(这里如果不理解可以自己拿笔模拟一下题目样例的过程)

通过二维的转移方程了解完怎么状态转移后,我们需要知道的是,其实二维数组在这题并不可行:
本题需要的 d p [ 1005 ] [ 200005 ] dp[1005][200005] dp[1005][200005] 数组是很容易会出现内存超限的情况的。

通过观察我们可以发现,其实这个转移方程,每次转移,也就只用了第i-1行和第i行,最后我们需要的也只有第n行的数据,所以,我们可以试图将其降维,降成只有两行的二维数组,这样就能不会有问题了:

//第0行即代表前面的前i-1份作业能产生的所有情况,第1行即代表前面的前i份作业能产生的所有情况
dp[0][0]=1;
for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=sum;j++)
        {
            if(dp[0][j]==1)
            {
                dp[1][j]=1;
                dp[1][j+a[i]]=1;
            }
        }
        for(int j=0;j<=sum;j++)  //对于下一行来说,当前行的结果则是其上一行的结果
            dp[0][j]=dp[1][j];
    }

要再进行优化,我们可以将数组降成一维:
我们可以将第二层循环倒过来,即从sum循环到0:

dp[0]=1;
for(i=1;i<=n;i++)
for(j=sum;j>=a[i];j--)
{
	if(dp[j-a[i]]==1)
		dp[j]=1;
}

这样做,第j位后面的都是当前行的结果,而前面则仍是上一行的结果,那我们前面的

	if(dp[i-1][j]==1)
		dp[i][j]=1;

在这就可以写成这样了:

	if(dp[j-a[i]]==1) //代表着前i-1份作业能组成j-a[i]分钟这种情况
		dp[j]=1;   //即前i份作业能组成j分钟这种情况

前面这一大段,在试图讲清背包和背包是如何降维的
如果没弄懂,可以去听听y总讲的:
https://www.bilibili.com/video/BV1X741127ZM
视频定位15分04秒

到这,我们也算是已经用背包把左手的所有情况都找出来了,那对于每种情况,我们用 s u m − i sum-i sumi 即可表示出右手写作业需要的时间了,那么两者取较大的那个则是当前情况下,小C写作业需要的时间了。

对于每种情况,取写作业时间最短的时间即可

上代码:

#include<stdio.h>
int n,sum=0,ans=0x3f3f3f3f,a[1005],dp[200005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	dp[0]=1;
	for(int i=1;i<=n;i++)
	for(int j=sum;j>=a[i];j--)
	{
		if(dp[j-a[i]])
			dp[j]=1;
	}
	for(int i=1;i<=sum;i++)
	{
		if(dp[i]) 
		{
			int l=i,r=sum-i,k;  //左右手当前情况写作业的时间
			if(l>r) k=l;   //k为写作业的时间,即左右手写作业时间的较大者
			else k=r;
			if(ans>k) ans=k;  //每种情况都对答案进行更新  
		}
	}
	printf("%d",ans);
	return 0;
}

C.小C的选择

剥去背景,这题其实也就是求选x个数,让其总和为8,有多少种选法,是一道深搜题

我们可以定义dfs函数为 d f s ( x , s u m ) dfs(x,sum) dfs(x,sum), x则表示当前这次选择选到哪一个任务,sum则表示当前已选的任务的总分为多少

那么,对于每一个任务我们都可以有选和不选两种情况,对此,我们都得将其考虑进去,然后再通过递归调用进入下一个任务选择判断,如果当前的所选总分已经达到8分,则进行计数并返回即可

上代码:

#include<stdio.h>
int n,ans,a[30];
void dfs(int x,int sum)
{
	if(sum==8)  //总和等于8,即可技术并返回
	{
		ans++;
		return;
	}
	if(x>n||sum>8) return;  
	dfs(x+1,sum+a[x]);   //选择当前的任务,任务总分加上a[i]
	dfs(x+1,sum);  //不选当前的任务,任务总分不变
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	dfs(1,0);  
	printf("%d",ans);
	return 0;
}

D.小C买泡芙(easy version)

作为easy version,这题的数据量特别小,因此这题只需模拟以每一个位置作为开始,买齐所有种类的泡芙需要买多少个即可,需要买的个数再乘价格即是要付的钱数

这里用v数组记录的是当前哪些种类的泡芙已经买过,记得每次换到下一个位置开始模拟前,需要清空一次v数组

上代码:

#include<stdio.h>
int i,j,n,m,k,ans=0x3f3f3f3f,a[1005],v[105];
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=n;i++)
	{
		int s=0;  //s记录当前已经买了多少种种类,每次一旦买齐所有种类就可以退出循环了
		for(j=1;j<=m;j++)  //清空v数组
			v[j]=0;
		for(j=i;j<=n;j++)   
		{
			if(v[a[j]]==0)   //如果这个种类的此前没有出现,则记录一下
			{
				v[a[j]]=1;
				s++;
			}
			if(s==m)   //种类已经买齐
			{
				int mon=(j-i+1)*k;   //计算价钱
				if(mon<ans) ans=mon;
				break;
			}
		}
	}
	printf("%d",ans);
	return 0;
}

E.小C买泡芙(hard version)

对于这题的数据量,前一题 O ( n 2 ) O(n^2) O(n2) 的时间复杂度的做法,在这就肯定会超时了。因此,这题需要换一个做法,用双指针来做。

双指针,指的是用两个指针(左指针、右指针)表示出一个区间,每次都要尽可能保证区间是符合题目条件的,并用区间的答案去更新最优的答案。

对于这题,双指针的做法即是:
在每次更新答案之前,需要将右指针不断右移,直到区间内所有种类的泡芙都已存在,保证了所有种类的泡芙都已存在,这个区间才算是符合条件的,便可以用于更新最终答案了。当区间符合条件后,左指针便也开始向右移,每次只移动一位,如果区间仍符合条件,则可以继续用区间的结果更新最终的答案;如果这个区间泡芙种类已不够,则返回至前面移动右指针。以此反复,直到把所有情况都考虑到,也就算完成了。

这道题也需要v数组对当前区间内的每种泡芙的数量进行统计,当一种泡芙的数量从0变成1,则区间内的泡芙种类则+1;当一种泡芙的数量从1变成0,则区间内的泡芙种类则-1。

知道了双指针怎么操作,也知道怎么统计区间内的泡芙种类数后,这题就可以算是能完成了。

上代码:

#include<stdio.h>
#define ll long long
int a[200005],v[200005];
int main()
{
	int i,n,m,k,s=0;
	ll ans=1e15;
	scanf("%d%d%d",&n,&m,&k);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int l=1,r=0;   //左右两个指针
	while(l<n)
	{
		while(s<m&&r+1<=n)   //当种类数不够的时候,右移右指针
		{
			v[a[++r]]++;   //记录当前种类的泡芙个数
			if(v[a[r]]==1) s++;   //当前种类数量从0到1,区间种类数+1
		}
		if(s==m)   //判断区间是否符合条件,符合即可更新答案
		{
			ll mon=1ll*(r-l+1)*k;
			if(mon<ans) ans=mon;
		}
		v[a[l]]--;   
		if(v[a[l]]==0) s--;
		l++;
		//将左指针右移一位,相对应的泡芙数量也要进行处理
	}
	printf("%lld",ans);
	return 0;
}

F.小C的快乐寒假

签到题,按着输出就行,注意一下 “ 的输出即可
上代码:

#include<stdio.h>
int main()
{
	printf("printf(\"Happy Winter Holiday!!!\\n\");\n");
	printf("cout<<\"Happy Winter Holiday!!!\"<<endl;\n");
	printf("Console.WriteLine(\"Happy Winter Holiday!!!\");\n");
	printf("System.out.println(\"Happy Winter Holiday!!!\");\n");
	printf("print(\"Happy Winter Holiday!!!\")\n");
	printf("fmt.Print(\"Happy Winter Holiday!!!\")\n");
	printf("Happy Winter Holiday!!!\n");
	return 0;
}

G.零食采购员小C

这题是一道差分题

对于每一个同学的需求区间 l , r l,r l,r ,我们只需要在一个求和序列中,对 s u m [ l ] sum[l] sum[l] 进行 + 1 +1 +1 标记;对 s u m [ r + 1 ] sum[r+1] sum[r+1] 进行 − 1 -1 1 标记,最后再对求和区间取前缀和,上述的操作就是差分。

这样操作后,我们就会发现,当前的求和序列就已经将每个同学需求区间统计好了!

例如:
给出一组数据:

2 5
3 6

标记后的求和序列:(数组从0开始)

0 0 1 1 0 0 -1 -1 0

对数组进行前缀和处理:(数组从0开始)

0 0 1 2 2 2 1 0 0

即是区间统计的结果

统计完选择每份零食各会有多少人快乐后,进行遍历更新答案,这题就完成了!

上代码:

#include<stdio.h>
int n,a,b,i,sum[100005];
int ans,maxx;
int main()
{
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		sum[a]++; sum[b+1]--;
	}
	for(i=1;i<=100000;i++)
	{
		sum[i]+=sum[i-1]; //求前缀和
		if(cf[i]>maxx)  //更新答案
		{
			maxx=cf[i];
			ans=i;
		}
	}
	printf("%d",ans);
	return 0;
}

H. 小C和反转数字

这题是次签到题

对于一个数字,如果它前后反转会出现不同,那他肯定就是0结尾的(第一次反转后0被当作前导零清掉了),此外,0反转后是能回到0的,需要特判输出YES就行

上代码:

#include<stdio.h>
int main()
{
	int n;
	scanf("%d",&n);
	if(n!=0&&n%10==0)
		printf("no");
	else printf("YES");
	return 0;
}

I.小C的回文矩阵

这题题目已经把回文矩阵的判断方法已经表达的十分清楚了,直接模拟题意判断每一层的是否回文即可。
不过,当我们把回文串的对应关系画图表示出来,这题就更简单了。
在这里插入图片描述
不同的颜色,对应着不同层的判断,而在同一层中,我们可以发现,回文串中需要判断相等的两个字符,其实是已矩阵的对角线对称的!那么,我们只需要对每个字符判断 s [ i ] [ j ] = = s [ j ] [ i ] s[i][j]==s[j][i] s[i][j]==s[j][i] 即可。

上代码:

#include<stdio.h>
int main()
{
	int t,n;
	char a[505][505];
	scanf("%d",&t);
	while(t--)
	{
		int i,j,judge=1;
		scanf("%d",&n);
		for(i=0;i<n;i++)
		    scanf("%s",a[i]);
		for(i=0;i<n;i++)
		for(j=0;j<n;j++)
		{
			if(a[i][j]!=a[j][i])  //如果一旦存在不相等,即可说明不是回文矩阵
			{
				judge=0;
				break;
			}
		}
		if(judge) printf("Yes\n");
		else printf("No\n");
	}
}

相比起来,这题如果直接按着题目意思来,直接模拟每一层的是否回文,就会显得十分复杂。
也放份代码大家看看吧:

#include <algorithm>
#include <cstring>
#include <cstdio>
#include <iostream>

using namespace std;

const int N  = 1e3 , M = 2e6 ;

int n,cnt_x;
char a[N][N] ;
char x[M],y[M];


bool check(int n,int s){   //进行回文判断
    if(n <= 0) return 0;
    int t = (s - n) / 2 ; 
    cnt_x = 0 ;
    for(int i = 1 ; i <= n ; i++) x[cnt_x++] = a[1+t][i+t] ;
    for(int i = 2 ; i <= n ; i++) x[cnt_x++] = a[i+t][n+t] ;
    for(int i = n -1  ; i >= 1 ; i --) x[cnt_x++] = a[n+t][i+t] ;
    for(int i = n - 1 ; i >= 1;  i--) x[cnt_x++] = a[i+t][1+t] ;
    memcpy(y,x,cnt_x + 1) ;
    reverse(y,y+cnt_x) ;
    x[cnt_x] = y[cnt_x] = 0 ;

    if(!strcmp(x,y)) return 1 ;
    return 0 ;
}

int main(){

    int t ;
    scanf("%d",&t) ;

    while(t--){
        scanf("%d",&n) ;

        for(int i = 1; i <= n ; i ++) scanf("%s",a[i] + 1) ;

        int t = n ;
        while(check(t,n)){
            t -= 2 ;
        }

        if(t > 0) puts("No") ;
        else puts("Yes") ;
    }
    return 0;
}

J.小C的又一个矩阵

这题属于简单思维题,需要想清楚矩阵中0、1需要怎么变换,才能很快的做出这题。

这题这里提供两种方法:

法一:

这题我们需要做的,是将0转换成1,那么我们可以想想如何将一个0变成1,而其他几个数字不改变。
要做到这一点,对于每个0,我们只需要3步就能实现了。这里直接上代码:

上代码:

#include<stdio.h>

int n,cnt;
int a[5] ;

int main(){
    for(int i = 1;  i<= 4 ; i++){
        scanf("%d",&a[i]) ;
        if(a[i]==0) cnt ++ ;
    }

    printf("%d\n",cnt * 3) ;

    for(int i = 1;  i<= 4 ; i++){
            if(a[i]==0){
                if(i == 1) printf("1 2 3\n1 3 4\n1 2 4\n") ;
                else if(i == 2) printf("1 2 3\n2 3 4\n2 1 4\n") ;
                else if(i == 3) printf("1 2 3\n2 3 4\n1 3 4\n") ;
                else printf("1 2 4\n1 3 4\n2 3 4\n") ;
            }
        }

    return 0;
}
法二:

第二种方法,则是利用从没有1到全是1的一个转换过程:0->3->2->1->4(1的个数)。那么,原先有多少个1,则对应进这个过程,先输出仍需要几步,再将转换过程表示出来即可

上代码:

#include<stdio.h>
int k,a[5],x,y;
int main()
{
    for(int i=1;i<=4;i++)
    {
        scanf("%d",&a[i]);
        k+=a[i];
    }
    if(k==0) printf("4\n");   //先输出需要多少步完成这个过程
    else if(k==3) printf("3\n");
    else if(k==2) printf("2\n");
    else if(k==1) printf("1\n");
    else printf("0\n");
    //虽然代码较长,但其实都是重复着类似的操作
    switch(k)    //因为这个过程是找到开始点,然后需要接着走完整个过程的,所以switch更合适
    {
        case 0:
            x=3,y=0;   //x代表需要更换的0的个数,y代表需要更换的1的个数
            for(int i=1;i<=4;i++)
            {
                if(x>0&&a[i]==0)   //将0换成1
                {
                    x--; a[i]^=1;
                    printf("%d ",i);
                }
                else if(y>0&&a[i]==1)   //将1换成0
                {
                    y--; a[i]^=1;
                    printf("%d ",i);
                }
            }
            printf("\n");
        case 3:
            x=1,y=2;
            for(int i=1;i<=4;i++)
            {
                if(x>0&&a[i]==0)
                {
                    x--; a[i]^=1;
                    printf("%d ",i);
                }
                else if(y>0&&a[i]==1)
                {
                    y--; a[i]^=1;
                    printf("%d ",i);
                }
            }
            printf("\n");
        case 2:
            x=1,y=2;
            for(int i=1;i<=4;i++)
            {
                if(x>0&&a[i]==0)
                {
                    x--; a[i]^=1;
                    printf("%d ",i);
                }
                else if(y>0&&a[i]==1)
                {
                    y--; a[i]^=1;
                    printf("%d ",i);
                }
            }
            printf("\n");
        case 1:
            x=3,y=0;
            for(int i=1;i<=4;i++)
            {
                if(x>0&&a[i]==0)
                {
                    x--; a[i]^=1;
                    printf("%d ",i);
                }
                else if(y>0&&a[i]==1)
                {
                    y--; a[i]^=1;
                    printf("%d ",i);
                }
            }
            printf("\n");
    }
}

K.小C的cf上分之路

本题是道简单模拟,模拟分数变化过程,需要注意对于上升中断的时候,计数并非归0,而是置为1即可

上代码:

#include<stdio.h>
int i,n,len,ans,a[200005];
int main()
{
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=n;i++)
	{
		if(a[i]>a[i-1])
			len++;
		else
		{
			if(len>ans) ans=len;
			len=1;
		}
	}
	printf("%d",ans);
	return 0;
}

题解到这应该也就算结束了,大家看完如果觉得哪里有疑问的,可以在下面的评论区留言~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值