距离上一次写贴还是在上次
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 ]∣}(0≤i≤n)
所以 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 …
做完了不要摸鱼,最好是在检查一下,万一发现了问题改对了,又能赚到分了
总而言之:
不 要 摸 鱼