CSP-X模拟赛二——孙晨佑补题报告

日期:2023.10.1    时间:9:30~11:30

学号:s09691   姓名:孙晨佑

一、题目分数(还不错👍):


T1【称心如意(satisfied)】:(Accpet)100

T2【AC万岁(acok)】:(Accpet)100

T3【解救达达(rescue)】:(Time Limit Exceeded)50

T4【整理文本(text)】:(Time Limit Exceeded)20

总分:(270/400) 比上次进步200分,开心。

 二、比赛过程:(挺出乎我意料的)

第一题(satisfied):

这题当时不是很自信,但当我看到n的取值范围才10^{3},就放开胆子做了。以及测试了极端数据的时候竟然正常显示了,没有不输出的情况,就觉得自己肯定能全对,最后也是正确了,也是非常开心好吧。

第二题(acok):

刚一读完题就感觉和前面的一个题特别像,都是求子串的,就非常有信心,当然也有点恐慌:没有测极端数据,其实就是懒得打,其实也是有一点赌的成分在里面。最后竟然对了,没有时间超限!意外之喜

第三题(rescue):

这题读完后感觉很简单,就是转成二进制后判断(不是前导零)零的个数就行了。但发现和前天的吉利数数据范围一样!!!都是10^{18},就想着能拿一点是一点分,本来就想着拿30~40分就没法再拿了,结果也是非常开心,得了50分。

第四题(text):

读完后脑子瞬间空了,笑死,根本不知道怎么做。想了一会后准备用模拟来做,但考虑到n和m取值范围都达到了2*10^{5},就想着一个时间复杂度尽量少的方法——最小化答案(二分),但没想到还是时间超限了(样例都对)。最终得了20分。

三、做题思路(重点)

第一题:称心如意(satisfied)

大意:一个正整数数字 N,得到一个称心如意的序列 S 与之匹配,称心如意的序列需要满足以下几个条件:

1、序列的长度为 N+1。

2、假设序列第 i 位取值为 j ( j 的范围为 1 到 9),那么需要满足N%j==0,并且需要满足 i 能整除 N/j,即i%(N/j)==0。

3、满足条件2的基础上,j 的取值应该尽量小。

4、若条件2不能满足,那么第 i 位输出一个 -

思路:

其实这题没啥难的,n的取值范围也不高,可以放心大胆用嵌套for,然后在里面判断那四个条件,直接输出即可,用char存也不是不行。

BUT!

千万不能用string存!

因为你还没有输入,你没法通过下标直接找到相应位置!

所以只能用char一维字符数组来存储。

以上两种方法各位读者可以根据自己的喜好挑选:

所以,上Accpeted代码(两种):

char存储:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int main(){
	int n,minn=0x3f3f3f3f;
	char a[10005];
	bool f=0;
	cin>>n;
	for(int i=0;i<=n;i++){
		f=0;
		minn=0x3f3f3f3f;
		for(int j=1;j<=9;j++){
			if(n%j==0&&i%(n/j)==0&&j<minn){
				a[i]=j+'0';
				f=1;
				break;
			}
		}
		if(!f) a[i]='-';
	}
	for(int i=0;i<=n;i++){
		cout<<a[i];
	}
	return 0;
}

直接输出:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int main(){
	int n,minn=0x3f3f3f3f;
	bool f=0;
	cin>>n;
	for(int i=0;i<=n;i++){
		f=0;
		minn=0x3f3f3f3f;
		for(int j=1;j<=9;j++){
			if(n%j==0&&i%(n/j)==0&&j<minn){
				minn=j;
				f=1;
			}
		}
		if(!f) cout<<'-';
		else cout<<minn;
	}
	return 0;
}

第二题:AC万岁(acok)

大意:给定一个字符串,计算ac作为字符串子序列出现的次数

思路:

这题其实也有两种方法:

1、for套个if,如果是a里面再套个for再套个if如果是c则cnt++,最后输出cnt就行了

1、单层循环,如果是a则cnt++,如果是c则让ans+=cnt,最后输出ans就行了

具体思路请参考“奶牛碑文”。

所以,上Accpeted代码(两种):

1:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int main(){
	string s;
	long long cnt=0,pos=0;
	cin>>s;
	int len=s.size();
	for(int i=0;i<len;i++){
		if(s[i]=='a'){
			pos=i+1;
			for(int i=pos;i<=len;i++){
				if(s[i]=='c') cnt++;
			}
		}
	}
	cout<<cnt;
	return 0;
}

2:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int main(){
	string s;
	long long cnt=0,ans=0;
	cin>>s;
	int len=s.size();
	for(int i=0;i<len;i++){
		if(s[i]=='a'){
			cnt++;
		}if(s[i]=='c') ans+=cnt;
	}
	cout<<ans;
	return 0;
}

第三题:解救达达(rescue)(这题虽然代码简短,但思路真的不好想啊喂!)

大意:怪兽最喜欢的数字就是1,而最害怕的数字是0。只要有足够多的 0 混入怪兽的食物,就可以击败怪兽。如果数字的二进制中有两个及以上的 0 (前导 0不算,即从第一个非0数表示二进制),怪兽不会吃掉这个数字。一个区间为 \left [ a,b \right ] 的数字,计算一下这个区间内有多少个数字会伤害到怪兽。

思路:

这题我们可以看到a和b的取值范围是10^{18}。(O(n)都会时间超限)

所以我们得用long long来存储

那我们来想,long long能存多少?————————10^{19}

​​​那显然够了,对8?

根据题意我们可以找到“转二进制”这个信息。

题目中我们又可以提取出“只有一个0的二进制数且在a和b之间的数才会被吃掉”这个信息。

那我们来思考:如何才能得到只有一个0的二进制数呢?

你可以想到的是用全是1的二进制数减去只有一个1的二进制数

那我们又来思考:如何才能得到全是1的二进制数呢?

你可以想到的是用嵌套for循环,i:1~63(全是一)(long long可以存8个字节(64位)),j:0~i-2(一个一)

再用一个变量存这个数

这个数:((1<<i)-1)-(1<<j)(错误的,后文会讲)

注意,这里有两个魔鬼细节:

1、既然a和b是long long类型的,那这个数肯定也是long long类型的,所以这里的1也不能是int类型的,得改成long long,所以是1ll才行,所以这个数其实是((1ll<<i)-1)-(1ll<<j)

2、[x,y]代表的是闭区间,包含x和y,开区间(不包含x,y)的是(x,y),所以判断条件为(假设那个数是num):if(num>=a&&num<=b)

先上Wrong Answer(加Wrong Answer主要原因是给大家一个前车之鉴,一次正确的就不发了):

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
#define ll long long
ll answer,size0,aa[100000],cnt;
bool flag=0,fl=0;
bool erjinzhi(ll n){
	while(n>0){
		int t=n%2;
		n/=2;
		aa[++cnt]=t;
	}
	for(int i=cnt;i>=1;i--){
		if(aa[i]==0&&flag==1){
			size0++;
		}
		if(aa[i]==1){
			flag=1;
		}
		if(size0>=2) return 0;
	}
	if(size0<2&&size0>0) return 1;
}
int main(){
	ll a,b;
	scanf("%lld%lld",&a,&b);
	flag=fl=answer=size0=cnt=0;
	memset(aa,0,sizeof aa);
	if(a==b){
		if(erjinzhi(a)){
			cout<<1;
			return 0;
		}else{
			cout<<0;
			return 0;
		}
	}
	for(ll i=a;i<=b;i++){
		flag=fl=size0=cnt=0;
		memset(aa,0,sizeof aa);
		if(erjinzhi(i)) answer++;
	}
	printf("%lld",answer);
	return 0;
}

Accpeted代码(超级少):

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
#define ll long long
int main(){
	ll a,b,cnt=0;
	scanf("%lld%lld",&a,&b); 
	for(int i=1;i<=63;i++){
	    for(int j=0;j<=i-2;j++){
	        ll m=((1ll<<i)-1)-(1ll<<j);
	        if(m>=a&&m<=b) cnt++;
	    }
	}
	printf("%lld",cnt);
	return 0;
}

第四题:整理文本(text)

大意:整理一份文本,必须使得文本整理在 M 行之内。文本中总共有 N 个单词,并且记录出来了每个单词的长度 l[i]​​。整理出来的文本必须要满足行首为当前行的第一个单词,相邻的两个单词需至少由一个空格间隔。按照要求整理结束之后,得到行宽(当前行中单词的 \sumL​[i]​​ 加 空格数量),使得其中最大的行宽最小,求对应的行宽值。

注意:单词不能调换顺序,并且单词不能舍弃一部分。

思路:

这题n和m包括l数组的取值范围实在是太大,所以普通的搜索显然不行,所以我们得使用时间复杂度为O(\log n)的搜索——二分!

而题目中说要使得其中最大的行宽最小,所以很显而易见是最大值最小化的二分答案。

所以check()函数是必须的(自己写,不是库函数)

check()函数代码如下:

bool check(ll x){
	ll sum=-1,cnt=1ll;//sum表示当前行宽,考虑空格问题设为-1;cnt表示当前行数,因为从1开始所以设为1
	for(ll i=1;i<=n;i++){
		if(l[i]+sum+1<=x){//可以放下当前单词
		    sum+=l[i]+1;//累加当前行宽
		}else{//不能放下当前单词
		    cnt++;//换行
		    sum=l[i];//sum赋给a[i],放置此单词
		}
	}
	return cnt<=m;//合法则返回true,否则返回false
}

 x表示当前查找到的列宽(mid),n表示单词个数,那个sum加上的1就是空格。

老规矩,先上Wrong Answer代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
const int N=2e5+5;
#define ll long long
ll sum;
ll l[N],n,m;
bool check(int x){
	int as=x,cnt=1;
	for(int i=1;i<=n;i++){
		if(as>=l[i]){
			as=as-l[i];
			if(as<=l[i+1]){
				cnt++;
				as=x;
			}else as--;
		}else{
			cnt++;
			as=x;
		}
	}
	if(cnt>m) return 0;
	else return 1;
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",l+i);
		sum+=l[i];
	}
	ll l=0,r=sum;
	while(l<r){
		int mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld",l);
	return 0;
}

再上Accpeted代码(代码里注释讲的还是比较详细的):

#include<iostream>
#include<cstdio>
using namespace std;
const int N=2e5+5;
#define ll long long
ll l[N],n,m,maxx,sum;
bool check(ll x){
	ll sum=-1,cnt=1ll;//sum表示当前行宽,考虑空格问题设为-1;cnt表示当前行数,因为从1开始所以设为1
	for(ll i=1;i<=n;i++){
		if(l[i]+sum+1<=x){//可以放下当前单词
		    sum+=l[i]+1;//累加当前行宽
		}else{//不能放下当前单词
		    cnt++;//换行
		    sum=l[i];//sum赋给a[i],放置此单词
		}
	}
	return cnt<=m;//合法则返回true,否则返回false
}
int main(){
	scanf("%lld%lld",&n,&m);//n表示单词个数、m表示最多可放m行
	for(int i=1;i<=n;i++){
		scanf("%lld",l+i);//输入每个单词的长度
		sum+=l[i];//累加后赋值给右端点
		sum+=1ll;
		maxx=max(maxx,l[i]);//求最大值赋给左端点
	}
	ll l=maxx,r=sum-1ll;
	while(l<r){//套用最小化模板
		ll mid=(l+r)>>1ll;//x>>1等价于x/2
		if(check(mid)) r=mid;//合法情况下,寻找更小的答案并保留此答案,改变右端点 
		else l=mid+1;//不合法情况下直接将此答案省略,改变左端点
	}
	printf("%lld",l);//l或r都行
	return 0;
}

 四、赛后总结

1、

这次整体偏简单,希望以后能坚持下去这种分数。当然,低级错误也不能去再犯,像freopen注释忘去掉、使用万能头文件这种就算是低级错误,以后更要加强注意才行。

2、

一定要记得打开、关上文件的框架: 

freopen(".in","r",stdin);
freopen(".out","w",stdout);
//要实现的代码
fclose(stdin);
fclose(stdout);

并且提交时要把 注释删掉!

好了,下篇博文见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值