【2022牛客OI赛前集训营-普及组(第四场)】总结

距离上一次写贴还是在上次

T1 字母简记

得分:100/100

通过率: 50.5 % 50.5\% 50.5%

我的做法:模拟

我的思路:模拟

牛客 T1 挺爱考模拟哈

其实难度不难,就是代码烦

在这道题中,我们不仅在输入时要准备一个输入用的 s s s 数组,还要准备一个 a n s ans ans 数组

当我们输入字符串 s s s 时,如果输入的是字母,则直接塞入 a n s ans ans 中,否则,输入的就是数字,我们就用处理快读的类似方法来处理这一串数字的十进制结果 s u m sum sum因为这个数字不一定是一位数),然后再把这个 a n s ans ans 数组复制 s u m sum sum

s s s 数组处理完后,直接输出 a n s ans ans 即可

BTW,蒟蒻在考试之间问了一下出题人,输入数据是不可能出现aaa0aa这种 0 0 0 单独出现情况的

代码:

#include<cstdio>
#include<cstring>
int T,n,len;
char a[500005],b[500005],c[500005];
int main(){
	scanf("%d",&T);
	while(T--){
		len=0;
		scanf("%d",&n);
		scanf("%s",a+1);
		for(int i=1;i<=n;){
			int sum=0;			//sum 作用上文已提
			while(a[i]>='0'&&a[i]<='9'){			//快读方式处理 sum
				sum=sum*10+a[i]-'0';
				i++;
			}
			if(sum!=0){			//sum 不为 0,说明需要开始复读
				for(int j=1;j<=sum;j++){			//复读 sum 遍
					for(int k=1;k<=len;k++){			//复制原 ans 数组
						c[(j-1)*len+k]=b[k];
					}
				}
				len*=sum;			//修改 ans 数组长度并重新赋值
				for(int j=1;j<=len;j++){
					b[j]=c[j];
				}
			}else{			//sum 为 0,说明读入字母,直接塞 ans 数组
				b[++len]=a[i];
				i++;
			}
		}
		for(int i=1;i<=len;i++){			//输出
			printf("%c",b[i]);
		}
		printf("\n");
	}
	return 0;
}

总结:

模拟题,注意细心,比较靠代码,但是牛客模拟其实都还好,想到就是赚到

T2 攻与防

得分:100/100

通过率: 13.7 % 13.7\% 13.7%

我的做法:前缀和

我的思路:前缀和

如此水的前缀和,通过率竟然如此之低!

准备两个数组 p r e 1 pre_1 pre1 p r e 2 pre_2 pre2 p r e 1 [   i   ] pre_1[\ i\ ] pre1[ i ] 表示第一个战士到第 i i i 个战士的攻击战斗力之和, p r e 2 [   i   ] pre_2[\ i\ ] pre2[ i ] 表示第 i i i 个战士到第 n n n 个战士的防御战斗力之和

求得两个数组后,假设我们在第 i i i 个点开始划分阵营,那么 w = p r e 1 [   i   ] , v = p r e 2 [   i + 1   ] w=pre_1[\ i\ ],v=pre_2[\ i+1\ ] w=pre1[ i ],v=pre2[ i+1 ] ,对应的 a n s = ∣ p r e 1 [   i   ] − p r e 2 [   i + 1   ] ∣ ans=|pre_1[\ i\ ]-pre_2[\ i+1\ ]| ans=pre1[ i ]pre2[ i+1 ] ,所以, O ( n ) O(n) O(n) 算法枚举 i i i ,得到最终答案 a n s = min ⁡ { ∣ p r e 1 [   i   ] − p r e 2 [   i + 1   ] ∣ } ( 0 ≤ i ≤ n ) ans=\min\{|pre_1[\ i\ ]-pre_2[\ i+1\ ]|\}(0\le i\le n) ans=min{pre1[ i ]pre2[ i+1 ]}(0in)

所以 CSL 的三分是怎么想到的?蒟蒻真的不理解

代码:

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
long long int n,ans=0x7f7f7f7f,a[100005],pre_1[100005],pre_2[100005];
int main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%1lld",&a[i]);
		pre_1[i]=pre_1[i-1];			//处理攻击战斗力的前缀和
		if(!a[i]){
			pre_1[i]+=i;
		}
	}
	for(int i=n;i>=1;i--){
		pre_2[i]=pre_2[i+1];			//处理防御战斗力的前缀和
		if(a[i]){
			pre_2[i]+=i;
		}
	}
	for(int i=0;i<=n;i++){			//枚举答案
		ans=min(ans,abs(pre_1[i]-pre_2[i+1]));
	}
	printf("%lld",ans);
	return 0;
}

总结:

一道比较简单的题,但是要注意 long long int(好像不开 long long int 也行),以防万一,最好开,万一爆了就无了

T3 四月是你的谎言

得分:100/100

通过率: 36 % 36\% 36%

我的做法:暴力枚举+区间选点

我的思路:摆烂 ⇛ \Rrightarrow 暴力枚举 ⇛ \Rrightarrow 暴力枚举+区间选点

很有意思的题

Part1:暴力枚举

首先,我想到对于输入的 s t r str str 字符串的每一位,我们都进行一次匹配,如果找到了匹配的单词,按照贪心的想法,把这个单词的最后一位改为*

贪心想法很简单,对于一个单词而言,因为我们是按照从左往右的顺序遍历,所以对于当前的单词的而言,右边的*比左边的*更有价值

所以,下面是暴力做法:

#include<cstdio>
#include<cstring>
int T,n,kkksc03;			//不要在意这个奇怪的变量名
char s[500005];
char a[15][15];
int len[15],N;
bool check(int I,int J){			//寻找匹配的单词
	if(I+len[J]-1>N){			//剩下的字符串长度还没有单词长
		return 0;
	}
	int sum=0,k=0,tot=-114514;			//sum 代表匹配了几个字符,k 代表该单词的第 k 个字母,tot 代表对应的在原字符串中的位置
	for(int i=I;i<=N;i++){
		if(sum==len[J]){			//单词长度已经匹配完毕
			break;
		}
		if(s[i]=='*'){			//有 * 字符
			continue;
		}
		sum++,k++,tot=i;;
		if(s[i]!=a[J][k]){			//找到有一个字母不匹配
			return 0;
		}
	}
	if(sum!=len[J]){			//因为有 * 字符,所以可能匹配的字符不及原单词长度,还需比较
		return 0;
	}
	kkksc03=tot;
	return 1;
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%s",a[i]+1);
			len[i]=strlen(a[i]+1);
		}
		scanf("%s",s+1);
		N=strlen(s+1);
		for(int i=1;i<=N;i++){
			if(s[i]=='*'){			//已经处理过了,跳过
				continue;
			}
			for(int j=1;j<=n;j++){
				if(check(i,j)){			//找到了有匹配的单词
					s[kkksc03]='*';			//处理最后一位
				}
			}
		}
		printf("%s\n",s+1);			//输出修改后的字符串
	}
	return 0;
} 

可惜,有问题

就拿题解的 Hack 而言:

在这里插入图片描述

所以,就需要新的想法

现在想想,好像只需要在原代码上改一下就过了

Part2:暴力枚举+区间选点

在这里插入图片描述

假设上图中的红色线段为原字符串,蓝色线段为需要修改的单词

因为所有需要修改的单词只需要修改一个字符即可,所以,现在问题变成了:

对于所有的蓝色线段,求出最少的点,使所有线段中至少包含一个点

是不是很熟悉?

是的,区间选点问题

所以,我们可以暴力找出所有的蓝色线段(即所有需修改的单词),再整一个区间选点就行了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,n,sum;
char a[15][15];
char s[500005];
int len[15],N,LEN;
struct node{
	int a,b;
	bool operator<(const node other){
		return b<other.b;
	}
}A[5000005];
bool flag[500005];
int main(){
	scanf("%d",&T);
	while(T--){
		sum=N=LEN=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%s",a[i]+1);
			len[i]=strlen(a[i]+1);
		}
		scanf("%s",s+1);
		N=strlen(s+1);
		for(int i=1;i<=N;i++){			//初始化
			flag[i]=A[i].a=A[i].b=0;
		}
		for(int i=1;i<=N;i++){
			for(int j=1;j<=n;j++){			//寻找匹配的单词
				if(i+len[j]-1>N){			//剩下的字符串长度还没有单词长
					continue;
				}
				bool OK=1;
				for(int k=1;k<=len[j];k++){
					if(s[i+k-1]!=a[j][k]){			//有一个单词不匹配就退出
						OK=0;
						break;
					}
				}
				if(OK){			//找到了匹配的单词
					A[++LEN].a=i,A[LEN].b=i+len[j]-1; 			//将该单词的左右端点塞进去
				}
			}
		}
		sort(A+1,A+1+LEN);			//区间选点模板
		sum=A[1].b;
		flag[A[1].b]=1;
		for(int i=1;i<=LEN;i++){
			if(A[i].a>sum){
				sum=A[i].b;
				flag[A[i].b]=1;
			}
		}
		for(int i=1;i<=N;i++){			//输出处理后的字符串
			if(flag[i]){
				printf("*");
			}else{
				printf("%c",s[i]);
			}
		}
		printf("\n");
	}
	return 0;
}

总结:

一道藏得比较深的贪心(?),很有意思,但是最先打的暴力似乎快是正解?还是要再尝试尝试

T4 嘤嘤的珂朵莉树

得分:0/100

死因: W A   a n d   R E \mathtt{WA\ and \ RE} WA and RE

通过率: 6.9 % 6.9\% 6.9%

我的做法:爆搜

我的思路:摆烂 ⇛ \Rrightarrow 爆搜 ⇛ \Rrightarrow 摆烂

想法本身很简单:

建树 ⇛ \Rrightarrow 找根节点 ⇛ \Rrightarrow 求深度 ⇛ \Rrightarrow 连辅助边 ⇛ \Rrightarrow 爆搜求答案 ⇛ \Rrightarrow 输出

写完就去摸鱼了,没有检查代码…

暂时没写出正解

总结:

打完暴力不检查,暴力打假了,悲

总的总结

预期应该是 > 300 > 300 >300 ,结果 = 300 =300 =300

做完了不要摸鱼,最好是在检查一下,万一发现了问题改对了,又能赚到分了

总而言之:

不 要 摸 鱼

祝大家 CSP 复赛 J 组人人 AK , S 组人人 1=

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值