新鲜出炉的 CSP-J 2021 复赛题目 题解

2021普及组复赛

代码量大点,难度一般。

T1 P7909 [CSP-J 2021] 分糖果

最简单容易想到的思路,应该就是枚举[L,R]范围内的每一个数字 %n 的结果,保留最大结果,如下所示。

//分糖果 
#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,L,MX=0;
	cin>>n>>L>>R;
	for(int i=L;i<=R;i++)
	{
		MX=max(MX,i%n);	
	}	
	cout<<MX;
	return 0;
}

有的同学可能还会优化一下,因为MX最大为 n-1

//分糖果 
#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,L,MX=0;
	cin>>n>>L>>R;
	for(int i=L;i<=R;i++)
	{
		MX=max(MX,i%n);	
        if(Mx==n-1)
            break;
	}	
	cout<<MX;
	return 0;
}

但是这两种方法都不是满分算法,时间复杂度分别为 o(R-L) 和 o(n) ,

结合数据来看的话,都是70分.

继续分析一下

实际问题就是要求 k % n 的最大值, k在 [L,R]范围内.

k % n 的结果,在[0,n-1]范围内。

随着k从L增加R,结果的变化 可以分为两种情况,

达到过 n-1 ,然后到 0 , 然后再继续增加。这种情况答案就是n-1

触顶过

一直增加,这个时候k取R最优

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-94PfyM6e-1635430011173)(NOIP%E7%9C%9F%E9%A2%98.assets/image-20211028211819828.png)]单调递增

那么 我们只需要判断 k % n 能不能增加到 n-1。

能的话结果是n-1,不能的话结果是R%n。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,L,R,t,MX=0;
	cin>>n>>L>>R;
	t=L%n;
	if(t+(R-L)>=n-1)
		cout<<n-1;
	else
		cout<<R%n;
	return 0;
}

这样o(1)的算法,就可以满分了

T2 P7910 [CSP-J 2021] 插入排序

这个题目可以模拟来做,也可以用树状数组。

考虑到普及组的同学学过树状数组的可能不太多,这里介绍模拟的做法。

这里设计到查询查询元素的原位置,所以用结构体存储值和原来的位置。

题目中有至多5000次的单点修改,每次修改完直接sort()排序时,复杂度为5000 * n * logn,会超时。

熟悉插入排序的话,会发现排序后的元素修改单个元素后的o(n)的时间内就可以完成调整使数组重新有序。单点修改的操作计算量可以优化为 5000 * n.

再结合数据范围来看,查询的次数比较多,而且查询的是原来某个元素排序后的位置,而且这种查询次数在 105 这个级别,所以我们可以把这个数据提前存储下来,从而实现 o(1) 的查询。

这样的话总计算量是 n* logn + 5000 * n + q,结合数据范围看,可以AC。

//插入排序 
/*

解题思路:
 利用结构体存储每个数字的值和它原来的位置  然后直接进行插入排序
 并用 cha数组 记录元素排序后的位置 
 当需要修改元素时:先找到元素所在位置 修改元素的值 并调整元素的位置 向前或者向后调整 o(n)
 查询:直接根据 cha[] 查询 o(1)
  
*/
#include<bits/stdc++.h>
using namespace std;
struct NUM{
 int val,pos;
}num[8010];
int n,cha[8010];//cha 数组方便查询 记录一下原来的第x个元素 现在 在哪个位置 
bool cmp(NUM a,NUM b)
{
	if(a.val<b.val)
		return 1;
	else if(a.val==b.val&&a.pos<b.pos)
		return 1;
	else
		return 0;
}
void gai(int x,int v)//把原来的 第x个元素改成v 然后调整数组元素顺序  
{
	num[cha[x]].val=v;
	
	int i=cha[x]; 
	//接下来可能需要向前调整顺序或者向后调整

	while(!cmp(num[i-1],num[i])&&i>1)	//向前调整
	{
		swap(num[i-1],num[i]);
		i--;
	} 
	
	while(!cmp(num[i],num[i+1])&&i<n)//向后调整 
	{
		swap(num[i],num[i+1]);
		i++;
	} 
 
	for(i=1;i<=n;i++)//记录下原来的第x的元素现在到哪了 
	{
		cha[num[i].pos]=i;
	}
 
}

int main(){
	int Q;
	cin>>n>>Q;
	for(int i=1;i<=n;i++)
	{
		cin>>num[i].val;
		num[i].pos=i;
	}
	sort(num+1,num+1+n,cmp);
	
	for(int i=1;i<=n;i++)//记录下原来的第x的元素现在到哪了 
	{
		cha[num[i].pos]=i;//原来的第pos个元素在下标 i 处 
	}
	while(Q--)
	{
		int q,x,v;
		cin>>q;
		if(q==1)
		{
			cin>>x>>v;
			gai(x,v);
		}
		else
		{
			cin>>x;
			cout<<cha[x]<<endl; 
		}
	}

	return 0;
}

T3 P7911 [CSP-J 2021] 网络连接

这也是一道模拟题。

核心在于验证一个IP地址的合法性。

细心验证题目里出现的各个问题即可。

同学们在做的时候除了单独验证某个问题外,还要注意自己的解题方法,会不会因为几个问题叠加在一起,会产生新的问题。

我的代码写法中就有一个 因为两个冒号相连,从而会有一个空数字,调了好久…

//P7911 [CSP-J 2021] 网络连接

/*
解题思路:模拟
	check()检查一个ip是否合法

*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
map<string,LL>mp;

char s[10],ip[100];
bool check(string a)//检查ip是否合法 
{
	string op,num;
	vector<string>all_num;
	for(int i=0;i<a.length();i++)//提取字符和数字 
	{
		if(ip[i]=='.'||ip[i]==':')
		{
			op.push_back(ip[i]);
			all_num.push_back(num);
			num.clear();	
		}
		else
		{
			num.push_back(ip[i]);
		}
		
	}
	all_num.push_back(num);//最后一个数字 单独存一下 
	num.clear();
	
	if(op=="...:")//字符顺序对 再检验数字 
	{
		for(int i=0;i<4;i++)
		{
			if(all_num[i].size()==0)//可能出现两个符号连着 从而出现空数字的情况 
				return 0;
			if(all_num[i].size()>1&&all_num[i][0]=='0')//含前导0 不合法 
				return 0;
			if(all_num[i].length()>3||(all_num[i].length()==3&&all_num[i]>"255")) 
				return 0;
		}
		if(all_num[4].length()>5 ||(all_num[4].length()==5&&all_num[4]>"65535")) 
			return 0;
		return 1;
	}
	else	//字符顺序不对 ip不合法 
	{
		return 0;
	}
	
}
int main(){
	
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%s %s",s,ip);
		if(!check(ip))
		{
			puts("ERR");
			continue;
		}
		if(s[0]=='S')
		{
			if(!mp[ip])
			{
				mp[ip]=i;
				puts("OK");
			}
			else
			{
				puts("FAIL");
			}
		}
		else
		{
			if(mp[ip]!=0)
				printf("%d\n",mp[ip]);
			else
				puts("FAIL");
		}
	}
	
	return 0;
}

T4 P7912 [CSP-J 2021] 小熊的果篮

还是模拟…

用双链表数据结构存储所有节点,然后单独存一下每个块的第一个元素编号。

每次输出一个块的头元素并将其删除,同时存储新块的开头的元素,这是一个o(n)的算法,下面直接看代码。

//小熊的果篮 
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int a[N],L[N],R[N]; 
int main(){
	int n;
	scanf("%d",&n);
	vector<int>b;
	for(int i=1;i<=n;i++)//初始化双链表 存储每个块的头元素 位置 
	{
		scanf("%d",&a[i]);
		if(i==1||a[i]!=a[i-1])
			b.push_back(i);
		L[i]=i-1;
		R[i]=i+1;
	}
	R[0]=1,L[n+1]=n;
	a[0]=a[n+1]=-1;
	
	while(R[0]!=n+1)//当双链表不空 
	{
		vector<int>c;
		for(auto u:b)
		{
			printf("%d ",u);//输出每个块的头结点 
			
			int x=L[u],y=R[u]; //删除已输出的 节点u 
			R[x]=y,L[y]=x;	
			
			if(a[u]==a[y]&&a[u]!=a[x])c.push_back(y);//u和左元素不同且和右元素相同 则是块的起点 就存下来 
		}
		puts("");
		swap(b,c);
	}
	return 0;
}

总体来说难度不高,居然没有dp的题目。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据提供的引用内容,CSP-J2021复赛有两道题目,分别是分糖果和小熊的果篮。 对于第一题分糖果题目来源是CCF,难度为入门。根据给出的代码,这是一个基于循环的算法,通过遍历[l,r]区间内的数,计算数对n取模后的最大值。具体的实现细节可以参考引用中的代码。这道题目属于入门级别,比较简单。 第二题是关于小熊的果篮。给定一个长度为n的数组a,其中连续的相同元素被视为一个块,要求按照块的顺序输出每个块的头元素,并删除已输出的元素。具体的实现细节可以参考引用中的代码。这道题目需要使用双链表来处理,时间复杂度为O(n)。 综上所述,CSP-J2021复赛题目包括分糖果和小熊的果篮,具体的解题思路和代码实现可以参考上述引用内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [[CSP-J 2021]比赛题解](https://blog.csdn.net/weixin_56550385/article/details/126811201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [新鲜出炉CSP-J 2021 复赛题目 题解](https://blog.csdn.net/qq_23109971/article/details/121024436)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值