2021-CSP-J2/S2 自我题解


比赛过后总有些意难平,所以写一篇自我题解来舒缓一下。

首先是CSP2021游记

10月23日 7:55
跟同学进入杭师大,参加普及组比赛。
8:30
拿到题目,总体扫了一遍,第一反应,我靠,这么水的题目。
12:00
出考场,由于第一次参加比赛,发挥失误,内心忐忑。
1:45
短暂休整了一段时间再次进入考场参加提高组比赛
2:30
正式开始,旁边一堆敲代码的声音,整场考试都没停下来过,看到第一题想了两个半小时,想出了一个伪算法,拿不了满分害怕了,匆忙看剩下三题,第二题区间DP一看情况这么多,人吓没了,输出样例,开始暴力打第三题,一看能骗28分高兴坏了,最后一题搁这阅读理解没看懂。
6:30
出考场,讨论一下思路,距离A题差一点点,太可惜了。
10月30日 8:25
分数出了,这天刚好是我生日,卡了半天的官网总算进去了,普及247,提高83。对于这次比赛的发挥我不太满意,可是这么差的发挥有这么点分我满意了[滑稽]。

正式题解:

先发一下题目
S组题面

          https://new.qq.com/omn/20211024/20211024A01S2X00.html

J组题面

          https://www.sohu.com/a/496778772_120132276?spm=smpc.content.content.2.1635687118672arFuYig

J组题解

第一题 分糖果(candy)

题意:

L − R L-R LR 颗糖果中选取 K K K 颗糖果,分给 n n n 个小朋友,要求 K m o d   n K mod\ n Kmod n 尽可能大。

题解:

说实话真没想到第一题这么水,简直就是签到题(口误) 就是签到题。
听说CF还出了一道一模一样的题[笑哭]。
根据题意,尽可能选较大的就行了,先判断最大值存不存在,若存在,直接输出,否则输出 R   m o d   n R\ mod\ n R mod n 即可。

参考代码
//本人考场代码,未修 
#include<bits/stdc++.h>
#define ll long long
#define FOR(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
void read(int &x)
{
	int ret=0;
	char c=getchar(),last=' ';
	while(!isdigit(c))
		last=c,c=getchar();
	while(isdigit(c))
		ret=ret*10+c-'0',c=getchar();
	x=last=='-'?-ret:ret;
}
int n,l,r;
int main()
{
	freopen("candy.in","r",stdin);
	freopen("candy.out","w",stdout);
	read(n);
	read(l);
	read(r);
	int x=l%n;
	if(x==n-1)
	{
		printf("%d\n",x);
		return 0;
	}
	x=(n-1)-x;
	if(l+x<=r)
	{
		printf("%d\n",n-1);
		return 0;
	}
	printf("%d\n",r%n);
	return 0;
}

第二题 插入排序(sort)

题意:

给你一个长度为 n n n 的数列,有 Q Q Q 次询问和修改,

  • 修改:改变某一个位置的数字(影响后续操作)
  • 询问:对当前数列进行插入排序,求原来某一位置上的数经排序后在哪一个位置(不影响后续操作)

插入排序代码,详见题目,(稳定,即相同大小比较顺序不变)。

题解:

本题给出的 Q ≤ 2 × 1 0 5 , n ≤ 8000 Q \leq 2\times 10^5 ,n \leq 8000 Q2×105,n8000 ,修改次数 ≤ 5000 \leq 5000 5000,对于每次查询都排序一次复杂度明显超标,能喜获52分。所以我们再进行思考,如果对于每次修改进行一次排序那就容易多了,我们脑袋一拍,发现对于每次修改,若之前是排序完整的数列,那么我们只需要将修改过的数往左右更新一下就好,开一个数组记录一下位置的改变输出答案就够了就可以了。
记得开始进行排序使得数列变得有序。so,水题+1;

参考代码:
#include<bits/stdc++.h>
using namespace std;
//By Spy_Savior
template<class T>inline void read(T&x){
	x=0;
	char c=getchar(),last=' ';
	while(!isdigit(c))last=c,c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	if(last=='-')x=-x;
}

const int MAXN=8e3+5;
int n;
int a[MAXN];
struct node{
	int id,val;
}b[MAXN],tmp[MAXN];
int ans[MAXN];

void merge_sort(int l,int r){
	if(l>=r)return;
	int mid=l+r>>1,i=l,j=mid+1,k=l;
	merge_sort(l,mid),merge_sort(mid+1,r);
	while(i<=mid&&j<=r){
		if(b[i].val<=b[j].val)tmp[k++]=b[i++];
		else tmp[k++]=b[j++];
	}
	while(i<=mid)tmp[k++]=b[i++];
	while(j<=r)tmp[k++]=b[j++];
	for(i=l;i<=r;i++)b[i]=tmp[i];
}

void solve(){
	for(int i=1;i<=n;i++)b[i].id=i,b[i].val=a[i];
	merge_sort(1,n);
	for(int i=1;i<=n;i++)ans[b[i].id]=i;
}

void update(int x,int v){
	int i;
	if(a[x]<v){
		for(i=ans[x]+1;i<=n;i++){
			if(b[i].val>v||b[i].val==v&&b[i].id>x)break;
			ans[b[i].id]--;
			b[i-1]=b[i];
		}
		i--;
		b[i].val=v,b[i].id=x;
		ans[x]=i;
	}
	else if(a[x]>v){
		for(i=ans[x]-1;i;i--){
			if(b[i].val<v||b[i].val==v&&b[i].id<x)break;
			ans[b[i].id]++;
			b[i+1]=b[i];
		}
		i++;
		b[i].val=v,b[i].id=x;
		ans[x]=i;
	}
}

int main()
{
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	read(n);
	int q;
	read(q);
	for(int i=1;i<=n;i++)read(a[i]);
	solve();
	for(int op,x,v;q--;){
		read(op);
		if(op==1){
			read(x),read(v);
			update(x,v);
			a[x]=v;
		}
		else{
			read(x);
			cout<<ans[x]<<'\n';
		}
	}
	return 0;
}

第三题 网络连接(network)

题意:

给出一些长度在 25 25 25 以内的字符串 s s s ,要求输入进来合法。
分为服务机和用户机,需要满足一系列条件(写不下去了,自己去看题面吧)。

题解:

堪称小型阅读理解加模拟题,不过和去年儒略日相比还是容易拿分的。
我们进行分类讨论:
首先格式不满足的都给它扔了:
主要是几段的情况和符号的情况,数字大小的情况和前导零的情况。暴力处理一下即可。判断条件,欢乐输出 E R R ERR ERR
①: S e r v e r Server Server
判断是否和前面的服务机撞机了,欢乐输出 F A I L FAIL FAIL
若都满足,则输出 O K OK OK

判断是否和客户机撞机或者找不到服务机,欢乐输出 F A I L FAIL FAIL
若都满足,则输出 O K OK OK
so,水题+1。
(如果有其他情况本人未考虑到的,欢迎留言,本人会进行更改)

参考代码
//扒了同学代码,我代码出了点小问题还没发现错误[滑稽] 
//他还考虑了时间复杂度问题,用字典树优化了一下复杂度,实测不写字典树也能过。
#include<bits/stdc++.h>
using namespace std;
//By Spy_Savior
template<class T>inline void read(T&x){
	x=0;
	char c=getchar(),last=' ';
	while(!isdigit(c))last=c,c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	if(last=='-')x=-x;
}

const int MAXN=1e3+5,MAXM=30;
char op[MAXM],ad[MAXN];
int n,num[10];

int findNum(int begin){
	if(begin>n)return -1;
	while(begin<=n&&isdigit(ad[begin]))begin++;
	return begin;
}

int getNum(int begin,int end){
	int ret=0;
	for(int i=begin;i<end;i++)ret=(ret<<3)+(ret<<1)+(ad[i]^48);
	return ret;
}

bool checkAd(){
	int begin=1,end;
	for(int i=1;i<=3;i++){
		end=findNum(begin);
		if(end==-1)return false;
		if(ad[end]!='.')return false;
		if(end-begin>1&&ad[begin]=='0'||begin==end||end-begin>3)return false;
		num[i]=getNum(begin,end);
		if(num[i]>255)return false;
		begin=end+1;
	}
	end=findNum(begin);
	if(end==-1)return false;
	if(ad[end]!=':')return false;
	if(end-begin>1&&ad[begin]=='0'||begin==end||end-begin>3)return false;
	num[4]=getNum(begin,end);
	if(num[4]>255)return false;
	begin=end+1;
	end=findNum(begin);
	if(end==-1)return false;
	if(end-begin>1&&ad[begin]=='0'||begin==end||end-begin>5)return false;
	num[5]=getNum(begin,end);
	if(num[5]>65535)return false;
	return end==n+1?true:false;
}

//Trie Tree
struct Node{map<int,int>nxt;int num;}Trie[MAXN<<2];
int cnt=1,tot=0;

bool insert(){
	int x=0,last=cnt;
	for(int i=1;i<=5;i++){
		if(!Trie[x].nxt[num[i]])Trie[x].nxt[num[i]]=cnt++;
		x=Trie[x].nxt[num[i]];
	}
	if(last==cnt)return false;
	Trie[x].num=tot;
	return true;
}

int find(){
	int x=0;
	for(int i=1;i<=5;i++){
		if(!Trie[x].nxt[num[i]])return -1;
		x=Trie[x].nxt[num[i]];
	}
	return Trie[x].num;
}

int main()
{
	freopen("network.in","r",stdin);
	freopen("network.out","w",stdout);
	int T;read(T);
	for(int tmp;T--;){
		tot++;
		scanf(" %s",op+1),scanf(" %s",ad+1);
		n=strlen(ad+1);
		if(!checkAd())puts("ERR");
		else if(op[1]=='S'){
			if(insert())puts("OK");
			else puts("FAIL");
		}
		else{
			tmp=find();
			if(tmp!=-1)cout<<tmp<<'\n';
			else puts("FAIL");
		}
	}
	return 0;
}
//色维yyds

第四题 小熊的果篮(fruit)

题意:

有若干个块,每次删除每个块的第一个元素(没有则不删),删去后每个相同颜色块合并。

题解:

题目还是很水的,直接暴力能喜获 30 30 30 分,即对每次操作进行模拟。
如果你想要拿到更高的分,需要使用链表和结构体来处理每次合并,在某水果块取完后将前后水果块链接,并将相同水果合并成同一个块。也可以使用vector模拟,储存每一块的块头(在有元素的情况下)。
这里我使用了两个数组来维护每个块的变化,再不断更新,除非没有元素了,每次将当前的块头输出即可。
so,水题+99999。

参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5; 
int n,a[MAXN],l[MAXN],r[MAXN];
vector<int> b;
int main() 
{
  scanf("%d", &n);
  a[0]=a[n+1]=-1;
  r[0]=1,l[n+1]=n;
  for(int i=1;i<=n;i++) 
  {
      scanf("%d",&a[i]);
      if(a[i]!=a[i-1]) 
	  	b.push_back(i);
      l[i]=i-1,r[i]=i+1;
  }
  while(r[0]!=n+1) 
  {
      vector<int> tmp;
      for(int i=0;i<b.size();i++) 
	  {
          printf("%d ",b[i]);
          int u=l[b[i]],v=r[b[i]];
          r[u]=v,l[v]=u;
          if(a[b[i]]!=a[u] && a[b[i]]==a[v]) 
		  	tmp.push_back(v); 
      }
      puts("");
      b=tmp;
  }
  return 0;
}

普及组总结

今年题目还是水。。。题目偏简单,所以分数线较高。
本人今年第一次参加 C S P CSP CSP 第二轮机试,感觉没发挥出来,每道题都只要动一动脑子就能写出来。。。甚至脑袋发热直接复制大样例测试导致没复制全,以为代码出来问题强行改成暴力做法白丢48分,哎,普及组可以说是翻车了!

S组题解

先挂在这里,慢慢补上去。
FLAG!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值