第四周PTA笔记 好吃的巧克力+特殊的翻译+下次一定(续)+走迷宫
好吃的巧克力
超市正在特价售卖巧克力,正好被贪吃的Lucky_dog看见了。
巧克力从左到右排成一排,一共有N个,M种。
超市有一个很奇怪的规定,就是你在购买巧克力时必须提供两个数字a和b,代表你要购买第 a 个至第 b 个巧克力(包含 a 和 b)之间的所有巧克力。
假设所有巧克力的单价均为1元。Lucky_dog想吃所有种类的巧克力,但又想省钱。作为Lucky_dog的朋友,他请你来帮他决定如何选择购买巧克力时的 a 和 b。
输入格式:
第一行包含两个正整数 N 和 M(M<=N, N<=10^6 , M<=2000),分别代表巧克力的总数及种类数。
第二行包含 N 个整数,这些整数均在1 至 M 之间,代表对应的巧克力所属的种类。
输出格式:
输出仅一行,包含两个整数a和 b(a<=b) ,由一个空格隔开,表示花费最少且包含所有种类巧克力的购买区间。
数据保证有解,如果存在多个符合条件的购买区间,输出a最小的那个。
输入样例:
12 5
2 5 3 1 3 2 4 1 1 5 4 3
输出样例:
在这里给出相应的输出。例如:
2 7
如果没有大神相助,凭我自己的算法将会因为超时一直过不了,这道题源于洛谷P1638逛画展,这里的题解更丰富些,但我还是想在此总结一下自己的思路。
思路:从第一个数字开始将右端点右移,直到包括1到M的每一个数字并计算由1到M每一个数字的出现的次数,再检查左端点,若出现次数两次及两次以上,则将左端点右移,并将该数字的次数减一。这样可以确定一个初始区间,(查看题解后)更加完善了一下自己的代码,就是将之后的数字再遍历,按照确定初始区间的步骤,检查是否能取一个更小的区间。(比较巧妙的技术方法也是向大佬学的)
#include<stdio.h>
int a[1000000],b[2005];//全局变量初始值为0
int main()
{
long int N;
int M,r=0,i,R=0,L=1,R1,L1;
scanf("%ld%d",&N,&M);
for(i=0;i<N;i++)
{
scanf("%d",&a[i]);
}
i=0;
while(r!=M){
if(b[a[i]]==0)r++;
b[a[i]]++;
i++;
R++;
}//确定一个初始右端点
while(b[a[L-1]]>1){
b[a[L-1]]--;
L++;
} //右移左端点,缩小区间
R1=R;
L1=L;//确定初始区间
while(i<N){
b[a[i]]++;
R++;
i++;
while(b[a[L-1]]>1){
b[a[L-1]]--;
L++;
}
if(R-L<R1-L1){
R1=R;
L1=L;
}
}//遍历检查是否有更小的区间
printf("%d %d",L1,R1);
return 0;
}
特殊的翻译
小明的工作是对一串英语字符进行特殊的翻译:当出现连续且相同的小写字母时,须替换成该字母的大写形式,在大写字母的后面紧跟该小写字母此次连续出现的个数;与此同时,把连续的小写字母串的左侧和右侧的字符串交换位置;重复该操作,直至没有出现连续相同的小写字母为止。现在小明想请你帮他完成这种特殊的翻译。
输入格式:
输入一串由小写字母构成的字符串。(字符串长度不大于250)
输出格式:
输出翻译后的字符串。
输入样例1:
dilhhhhope
输出样例1:
在这里给出相应的输出。例如:
opeH4dil
输入样例2:
lodnkmgggggggoplerre
输出样例2:
在这里给出相应的输出。例如:
eG7lodnkmR2ople
刚开始思路很乱,主要在想字符串不相等如果再同一个数组当中不好处理,又或者在字符串中出现了好几个连续的字符串这也给我的解题思路蒙上了一层雾(就是思路很迷茫的委婉说法),在学习了其他同学的解题思路后,有了一个比较清晰的解题步骤了,现在先交代一下我的思路。
思路:首先题目并没有我所想的那么复杂,先找到第一个连续的字符串将其左右两边的字符串交换并且改变这个连续的字符串,接着再继续遍历,若有遇到一个连续字符串则重复以上步骤,直到整个字符串中没有连续的小字符串。大致思路就是这样,也有了个整体的框架。接着就是对于字符串交换的处理了,我新开了三个字符数组,分别用来存放左边字符,改变字符,右边字符。接着有个很重要的一步,就是将原本的字符串清零,然后再以右边字符,改变字符,左边字符的顺序赋给新的字符串。
感觉整体代码都很重要,我就直接附上全部代码了,在代码中我也将一些容易被忘记和忽略的情况标注出来了,方便解读。
#include<stdio.h>
#include<string.h>
char s1[250],s2[5],s3[250],s[250];
int main()
{
int n,i,t=1,a,b,x,j;
scanf("%s",s);
n=strlen(s);
for(i=0;i<n-1;i++)
{
if(s[i]==s[i+1]&&s[i]<123&&s[i]>96){//连续的字符串得是小写字母,必须附上这一判断,不然当字符个数为22或33时会出现差错
while(s[i]==s[i+1]){
t++;
i++;
}
a=i-t+1;
b=i+1;
for(x=b,j=0;s[x]!='\0';x++)
{
s3[j]=s[x];
j++;
}
strncpy(s1,s,a);
s2[0]=s[i]-32;
i=1;
memset(s,'\0',n);
if(t<10){
s2[i]=t+48;
n=a+2+j;
}
else if(t>9){
s2[i]=t/10+48;
i++;
s2[i]=t%10+48;
n=a+3+j;
}//对于连续的字符个数大于9的处理
for(i=0;i<j;i++)
{
s[i]=s3[i];
}
if(t<10){
s[i]=s2[0];
i++;
s[i]=s2[1];
i++;
}
else if(t>9){
s[i]=s2[0];
i++;
s[i]=s2[1];
i++;
s[i]=s2[2];
i++;
}//这一部分写得比较繁琐了,优点就是方便理解,嘿嘿嘿
for(x=0;x<a;x++)
{
s[i]=s1[x];
i++;
}
i=-1;//进入新的for循环后会i++,之后就会变为0
t=1;
}
}
printf("%s",s);
return 0;
}
下次一定(续)
你是一个bilibili的六级号,由于经常一键三连,所以一个硬币都没有,现在你又做了个梦,在梦中你制定了一个硬币增加规则:
第一天登陆后硬币总数1个,第二天登陆后硬币总数112个,第三天登陆硬币总数112123个......,以此类推,梦中不知日月,你瞬间拥有了11212312341234512345612345671234567812345678912345678910123456789101112345678910......无穷多个硬币。
常年维护B站秩序的百漂怪看不下去了,决定随机挑一个数x,作为这个无穷数的第x位,如果你能正确答对这第x位上的数是什么,就赠送给你与这个数等量的硬币。
百漂怪总共会挑t次。
你决定编程模拟一下,搏一搏,单车变摩托。
输入格式:
第一行包含一个正整数t(1≤t≤10),表示百漂怪挑了t次。 接下来t行,每行一个正整数x (1 ≤ x≤ 2^31-1),表示第i次,百漂怪挑的是第x位。
输出格式:
输出共t行,每行输出对应第x位上的数。
输入样例1:
2
3
8
输出样例1:
2
2
输入样例2:
6
222
66666
99999999
66656565
22222
2
输出样例2:
6
4
9
4
1
1
思路:一开始完全暴力解法,于是时间超出,就采用了半暴力解法(自创名词哈哈哈)。由于这组数据很大,我们不能将它全部存于一个数组,所以我们得一部分一部分地存。一开始先判断百漂怪所给的数字是属于哪一部分的数,比如3,那前个数是112,那这就是第二部分的数字,不知道我有没有说清楚,但题目意思大概就是酱紫。判断完了之后判断这个最初的数是这部分的哪一个数字,最后得出结果。
一开始判断是哪一部分的数字我由于写得太复杂了,时间超出,我是直接用一个数字n一直递增来减去最初的数字。
//错误代码
scanf("%d",&x);
j=1;
while(x>0){
if(j<10){
n++;
x-=n;
j++;
}
else if(j<100&&j>9){
n+=2;
x-=n;
j++;
}
else if(j>99&&j<1000){
n+=3;
x-n;
j++;
}
else if(j>999&&j<10000){
n+=4;
x-n;
j++;
}
else if(j>9999&&j<100000){
n+=5;
x-n;
j++;
}
}
x+=n;
后来这部分我改进了一下,时间超出的问题就解决了,虽然也不是特别高明。我现在还太菜鸡,其实对于算法时间复杂度的概念还很模糊。
//正确代码
for(i=0;i<32000;i++)
{ y=i;
if(i==0)a[i]=1;
else a[i]=a[i-1]+(int)(log10(y+1)+1);
}//存位数
for(j=0; ;j++)
{
x-=a[j];
if(x<=0)break;
}
x+=a[j];
通过其他同学的博客,直到这个数据总共有31268组,于是我将后面的判断由100以内,扩大到了100000以内,以下是判断具体数字的代码。
j=1;
for(r=0; ;j++)
{
if(j<10){
n=j;
r++;
if(r==x)break;
}
else if(j>9&&j<100){
n=j/10;
r++;
if(r==x)break;
n=j%10;
r++; if(r==x)break;
}
else if(j>99&&j<1000)
{
n=j/100;
r++;if(r==x)break;
n=j/10%10;
r++;if(r==x)break;
n=j%10;
r++;if(r==x)break;
}
else if(j>999&&j<10000)
{
n=j/1000;
r++;if(r==x)break;
n=j%1000/100;
r++;if(r==x)break;
n=j%100/10;
r++;if(r==x)break;
n=j%10;
r++;if(r==x)break;
}
else if(j>9999&&j<100000)
{
n=j/10000;
r++;if(r==x)break;
n=j%10000/1000;
r++;if(r==x)break;
n=j%1000/100;
r++;if(r==x)break;
n=j%100/10;
r++;if(r==x)break;
n=j%10;
r++;if(r==x)break;
}
}
printf("%d\n",n);
走迷宫
你正在玩一个迷宫游戏,迷宫有n×n格,每一格有一个数字0或1,可以从某一格移动到相邻四格中的一格上。为了消磨时间,你改变了玩法,只许从0走到1或者从1走到0。
现在给你一个起点,请你计算从这个格子出发最多能移动多少个格子(包含自身)。
输入格式:
第1行包含两个正整数n和m(1≤n≤1000,1≤m≤10000)。
接下来n行,对应迷宫中的n行,每行n个字符,字符为0或者1,字符之间没有空格。
接下来m行,表示m次询问。每行2个正整数i,j,表示从迷宫中第i行第j列的格子开始走。
输出格式:
输出共m行,每行一个整数,分别对应于输入数据中的m次询问,给出最多能移动的格子数。
输入样例:
2 2
01
10
1 1
2 2
输出样例:
4
4
因为这道题让我的这篇博客居然拖到今天才写完,一开始对于没接触过这样题型的我的思路就是一个一个地找,对,没错,酱紫是行不通的。于是在多次查阅资料下了解了dfs和bfs,其实我也没有理解透,但勉勉强强能应付这题吧。这一题的原题在P1141迷宫并且一起在此打包上题解了。这个up主的视频也有助于理解dfs和bfs,个人感觉他的dfs讲得好一些。
一些注意事项和解题思路我就在代码当中直接标记出来了,因为这次的解题就是主要用到dfs(通过递归实现),所以感觉也没啥好说的。
#include<stdio.h>
#include<string.h>
int n,b[1005][1005],z[100005],i;//z数组是存每一个能够走的最大距离,需要开得大一点,不然会出现RE的问题
char s[1005][1005];
void dfs(int x,int y,int f);
int main()
{
int m,c,d;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
{
scanf("%s",s[i]);//一开始用%c来存,一直输出不了,应该是因为一行之间的0或1没有其他字符隔开,无法判断输出结束了吧
}
memset(b,-1,sizeof(b));//将b数组初始化为-1,这为了判断是否有走过这一格的数组。一开始初始化为0,但是当i=0时这个就没法走了[哭脸]
for(i=0;i<m;i++)
{
scanf("%d%d",&c,&d);
c--;
d--;
if(b[c][d]==-1)dfs(c,d,s[c][d]-48);//将字符数组转换为0和1比较方便于判断
else z[i]=z[b[c][d]];//如果b数组已经被赋值,说明已经走过了这个各自,可以通过已经存储在a中的数字来读出,可以节省时间
}
for(i=0;i<m;i++)
{
printf("%d\n",z[i]);
}
return 0;
}
void dfs(int x,int y,int f)
{
if(x<0||x>=n||y<0||y>=n)return;//判断是否在迷宫范围内;
else{
if(b[x][y]==-1&&s[x][y]-48==f){//判断该格子是否走过且是否与上一个格子的字符相同
b[x][y]=i;
z[i]++;
dfs(x+1,y,!f);//这个!f得想一想,就是取上一个格子的字符的相反数,体现了将字符元素改为0和1的好处了吧
dfs(x-1,y,!f);
dfs(x,y+1,!f);
dfs(x,y-1,!f);
}
else return;
}
}
如果有写的不好或说的错误的地方,欢迎指出哦。