日期: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的取值范围才,就放开胆子做了。以及测试了极端数据的时候竟然正常显示了,没有不输出的情况,就觉得自己肯定能全对,最后也是正确了,也是非常开心好吧。
第二题(acok):
刚一读完题就感觉和前面的一个题特别像,都是求子串的,就非常有信心,当然也有点恐慌:没有测极端数据,其实就是懒得打,其实也是有一点赌的成分在里面。最后竟然对了,没有时间超限!意外之喜
第三题(rescue):
这题读完后感觉很简单,就是转成二进制后判断(不是前导零)零的个数就行了。但发现和前天的吉利数数据范围一样!!!都是,就想着能拿一点是一点分,本来就想着拿30~40分就没法再拿了,结果也是非常开心,得了50分。
第四题(text):
读完后脑子瞬间空了,笑死,根本不知道怎么做。想了一会后准备用模拟来做,但考虑到n和m取值范围都达到了,就想着一个时间复杂度尽量少的方法——最小化答案(二分),但没想到还是时间超限了(样例都对)。最终得了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
数表示二进制),怪兽不会吃掉这个数字。一个区间为 的数字,计算一下这个区间内有多少个数字会伤害到怪兽。
思路:
这题我们可以看到a和b的取值范围是。(O(n)都会时间超限)
所以我们得用long long来存储
那我们来想,long long能存多少?————————。
那显然够了,对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]。整理出来的文本必须要满足行首为当前行的第一个单词,相邻的两个单词需至少由一个空格间隔。按照要求整理结束之后,得到行宽(当前行中单词的 L[i] 加 空格数量),使得其中最大的行宽最小,求对应的行宽值。
注意:单词不能调换顺序,并且单词不能舍弃一部分。
思路:
这题n和m包括l数组的取值范围实在是太大,所以普通的搜索显然不行,所以我们得使用时间复杂度为O()的搜索——二分!
而题目中说要使得其中最大的行宽最小,所以很显而易见是最大值最小化的二分答案。
所以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);
并且提交时要把 注释删掉!
好了,下篇博文见!