程序设计思维与实践 CSP-M2 补题
A - HRZ 的序列
题目描述:
相较于咕咕东,瑞神是个起早贪黑的好孩子,今天早上瑞神起得很早,刷B站时看到了一个序列 ,他对
这个序列产生了浓厚的兴趣,他好奇是否存在一个数 ,使得一些数加上 ,一些数减去 ,一些数不
变,使得整个序列中所有的数相等,其中对于序列中的每个位置上的数字,至多只能执行一次加运算或
减运算或是对该位置不进行任何操作。由于瑞神只会刷B站,所以他把这个问题交给了你!
分析
这道题题意中的意思九四最多满足条件的就是三个数,并且三个数还是等差数列,如果多余三个,肯定不会成功,少于三个数,就肯定成功,我们只需要记录数据的分布,并且分析一下就可以得出结论,我使用了map,来对于所有的数据进行记录,最后只需要判断map里的数量就可以了,并且在三个数据的时候,因为map自动排序,可以直接按序拿出判断等差
注意:**
数据量可能较大,所以要使用long long ,否则有一组会不过**
代码
#include<iostream>
#include<map>
using namespace std;
const int nmax=1E4+10;
int nn[nmax];
int judge[4];
int main(){
int num;
scanf("%d",&num);
while(num--){
map<long long ,int> mp;
int n;long long tmp;scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%lld",&tmp);
if(mp.find(tmp)==mp.end())mp[tmp]=1;
else mp[tmp]++;
}
if(mp.size()>3) printf("NO\n");
else if(mp.size()<=2){
printf("YES\n");
}
else {
int k=0;
for(auto iter=mp.begin();iter!=mp.end();iter++){
judge[k++]=iter->first;
}
if(judge[2]+judge[0]==2*judge[1]) printf("YES\n");
else printf("NO\n");
}
}
return 0;
}
B - HRZ 学英语
题目描述
瑞神今年大三了,他在寒假学会了英文的26个字母,所以他很兴奋!于是他让他的朋友TT考考他,TT想
到了一个考瑞神的好问题:给定一个字符串,从里面寻找连续的26个大写字母并输出!但是转念一想,
这样太便宜瑞神了,所以他加大了难度:现在给定一个字符串,字符串中包括26个大写字母和特殊字
符’?’,特殊字符’?'可以代表任何一个大写字母。现在TT问你是否存在一个位置连续的且由26个大写字
母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现
的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果
不存在,输出-1! 这下HRZ蒙圈了,他刚学会26个字母,这对他来说太难了,所以他来求助你,请你帮
他解决这个问题,报酬是可以帮你打守望先锋。
说明:字典序 先按照第一个字母,以 A、B、C……Z 的顺序排列;如果第一个字母一样,那么比较第二
个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,SIGH 和 SIGHT),那么把短者排
在前。例如
AB??EFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ
上面两种填法,都可以构成26个字母,但是我们要求字典序最小,只能取前者。
注意,题目要求的是 第一个出现的,字典序最小的!
-
输入格式
输入只有一行,一个符合题目描述的字符串 -
输出格式
输出只有一行,如果存在这样的子串,请输出,否则输出-1 -
input1:
ABC??FGHIJK???OPQR?TUVWXY? -
output1:
ABCDEFGHIJKLMNOPQRSTUVWXYZ -
input2:
AABCDEFGHIJKLMNOPQRSTUVW??M -
output2:
-1
题目分析
- 使用类似尺取的方法,保持一个大小为26个字母的窗口,并且不断地向右移动一格,并且每次判断格内的字符串是否符合要求,符合要求则进行输出,直到到达结尾.
- 对于窗口内字符串的判断,我记录了窗口内"?"的数量,并且在每个窗口中,计算当前窗口的字符种类数,如果这两个数字加起来为26,则说明可以输出了,转到输出函数中进行
- 输出正确的字符串时,我们要对窗口进行遍历,如果是A-Z的字符,就输出字符,如果不是我们就从记录当前窗口数组从A开始遍历,找到第一个当前窗口中含有数量为0的,即当前窗口不存在的字符,并且输出它,输出后,下次遇到"?"从这个字符的下一个开始找下下一个含有数量为0的字符,这样每次遇到?,都会输出一个最小的且不存在在当前窗口的字符.
注意:在窗口小于26时直接输出-1
代码
#include<iostream>
#include<cstring>
using namespace std;
const int nmax=1E6+10;
char mystr[nmax];
int mp[26],count=0;//num of "?"
void init(){memset(mp,0,sizeof(mp));}
int get(){
int res=0;
for(int i=0;i<26;i++){
if(mp[i]>0) ++res;
}
return res;
}
void output(int l){
int ch=0;
while(mp[ch]>0&&ch<26) ch++;
for(int i=0;i<26;i++){
if(mystr[l+i]!='?') {putchar(mystr[l+i]);}
else {
putchar(ch+'A');
ch++;
while(mp[ch]>0&&ch<26) ch++;
}
}
}
int main(){
init();
cin.getline(mystr,nmax);
int l=0,r=25,len=strlen(mystr);
if(len<26) {printf("-1"); return 0;}
for(int i=0;i<26;i++){
if(mystr[i]=='?') count++;
mp[mystr[i]-'A']++;
}
bool flag=false;
//printf("%d",len);
while(r<len){
if(get()+count==26) {output(l);flag=true;break;}
++l,++r;
if(r==len) break;
if(mystr[l-1]=='?') count--;
else mp[mystr[l-1]-'A']--;
if(mystr[r]=='?') count++;
else mp[mystr[r]-'A']++;
}
if(!flag) printf("-1");
return 0;
}
C - 咕咕东的奇妙序列
题目描述
咕咕东 正在上可怕的复变函数,但对于稳拿A Plus的 咕咕东 来说,她早已不再听课,此时她在睡梦中
突然想到了一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其
中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所
有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是
11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是
5,第38项是2,第56项是0。咕咕东 现在想知道第 k 项数字是多少!但是她睡醒之后发现老师讲的东西
已经听不懂了,因此她把这个任务交给了你。
- 输入由多行组成。
第一行一个整数q表示有q组询问
接下来第i+1行表示第i个输入 ,表示询问第k(i) 项数字(ki<1E18) - 输出
输出包含q行 第i行输出对询问 的输出结果。 - input1
513
20
38
56 - output1
1
2
5
2
0
题目分析
- 这道题数据量很大,达到了1E18,如果我们使用string暴力求解到第七个就挂了,所以由于要处理1E18,我们就要采取分块的思想,把1E18的数据分成一些小块,来判断给出的数在哪些小块中,并且在小块中的一些固定的属性进行求解.
- 首先我们要对分块的要求进行定义,假设每次都是12345…k(i),那么我对k(i)的位数进行分类,分为1位,2位,直到9位,这样查询的时候就够用了,之后根据不同的块只需加上(位数*个数)就可以迭代得到下一个位的个数,利用这个数组,我们也可以轻易的算出每一位的从1,12,123 …999(i个9)的所用的位数,这样我们就获得了一个数组来进行二分
- 建立好数组后,我就开始对于要查询的数字进行二分搜索,首先,利用一个9位的进行确定第一大分块,即他是存在在112123…到多少位的分块中的,因为这个数组只有9个,我就没二分,只是遍历求的.
int len_i=-1;
//找寻位数
//可以使用二分,但没必要
/*
while(l<=r){
int mid=(l+r)>>1;
if(roll[mid]>mynum){r=mid-1;}
else {l=mid+1,len_i=mid;}
}*/
for(int i=1;i<10;++i){
if(roll[i]>=mynum){ len_i=i;break;}
}
if(len_i==-1) return 0;
//找寻**
tmp1=mynum-roll[len_i-1];
- 设求得位数是k,则我们需要知道目标数十载它在到自己内部的哪一位的即他有可能是一个k位的,但是他的1…k位遍历时都有可能,因为这个范围取决于我们求得k的位数,而这个范围可能十分的大,所以使用二分求得,我们计算到m所占的位数,以此来进行比较,得到这个遍历的数字m.
long long l=1,r=9*pow(10,len_i-1);
long long len_j=-1;
while(l<=r){
long long mid=(l+r)>>1;
long long key=mid*que[len_i-1]+mid*(mid+1)/2*len_i;
if(key>=tmp1){r=mid-1;len_j=mid;}
else {l=mid+1;}
//printf("l=%d,r=%d\n",l,r);
}
//printf("len_j=%lld\n",len_j);
- 在得到m后,将前面的所占位数减去,就只剩下一个12345…到m的序列了,而我们之前也记录了到不同位数的所占的位数,我们就可以求得它的真实的位数,在减去前面的序列后,就是一个都是同一个位数的序列了,这时我们就很好计算了,
long long tmp=a-que[len-1]*(len2-1)-len2*(len2-1)/2*len;
int real_len=1;
while(real_len<=len&&tmp>que[real_len]) real_len++;
//printf("real_len=%d,tmp=%lld,q=%lld\n",real_len,tmp,que[real_len]);
long long num_on=tmp-que[real_len-1]-1;
//printf("num_on=%lld\n",num_on);
int real_num=num_on/real_len;
//printf("real_num=%d\n",real_num);
int left=real_len-num_on%real_len;
int res=pow(10,real_len-1)+real_num;
//printf("res=%d\n",res);
res/=(int)pow(10,left-1);
res=res%10;
printf("%d\n",res);
代码
#include<iostream>
#include<cstring>
#include<math.h>
using namespace std;
const int nmax=18+2;
const int mmax=1E6+10;
char str_num[nmax];
long long que[nmax],roll[nmax];
//que[i]是从1--i位,所有的位数,如que[1]=9 123456789
//roll[i]是真实的位数如roll[1]=45 1121231234123451.....123456789
void init(){
que[0]=0,roll[0]=0;
for(int i=1;i<10;++i){//到的最多9位,真实位数为19位,够用
long long num=(long long)9*pow(10,i-1);
que[i]=que[i-1]+num*i;
roll[i]=roll[i-1]+que[i-1]*num+(num+1)*num/2*i;
}
}
inline void output(long long a,int len,long long len2){//len是到的位数,len2是到的数
long long tmp=a-que[len-1]*(len2-1)-len2*(len2-1)/2*len;
int real_len=1;
while(real_len<=len&&tmp>que[real_len]) real_len++;
//printf("real_len=%d,tmp=%lld,q=%lld\n",real_len,tmp,que[real_len]);
long long num_on=tmp-que[real_len-1]-1;
//printf("num_on=%lld\n",num_on);
int real_num=num_on/real_len;
//printf("real_num=%d\n",real_num);
int left=real_len-num_on%real_len;
int res=pow(10,real_len-1)+real_num;
//printf("res=%d\n",res);
res/=(int)pow(10,left-1);
res=res%10;
printf("%d\n",res);
}
int main(){
char mystr[nmax];
init();
long long int mynum,tmp1;
//printf("%lld",roll[9]);//19位
int round;scanf("%d",&round);
while(round--){
scanf("%s",mystr);
sscanf(mystr,"%lld",&mynum);
int len_i=-1;
//找寻位数
//可以使用二分,但没必要
/*
while(l<=r){
int mid=(l+r)>>1;
if(roll[mid]>mynum){r=mid-1;}
else {l=mid+1,len_i=mid;}
}*/
for(int i=1;i<10;++i){
if(roll[i]>=mynum){ len_i=i;break;}
}
if(len_i==-1) return 0;
//找寻**
tmp1=mynum-roll[len_i-1];
//printf("tmp=%lld\nlen_i=%d\n",tmp1,len_i);
long long l=1,r=9*pow(10,len_i-1);
long long len_j=-1;
while(l<=r){
long long mid=(l+r)>>1;
long long key=mid*que[len_i-1]+mid*(mid+1)/2*len_i;
if(key>=tmp1){r=mid-1;len_j=mid;}
else {l=mid+1;}
//printf("l=%d,r=%d\n",l,r);
}
//printf("len_j=%lld\n",len_j);
output(tmp1,len_i,len_j);
}
return 0;
}
(最后一题差点把我搞死…)