第二次CSP模拟

第二次CSP模拟:

第一题:HRZ的序列

1.题意简介:HRZ刷b站的时候看到了这样一个序列:一个长度为n的序列,问是否存在这样一个数k,使得序列中一些数加上这个数 ,一些数减去这个数,一些数不变,使得整个序列中所有的数相等(其中对于序列中的每个位置上的数字,至多只能执行一次加运算或减运算或是对该位置不进行任何操作)。

输入格式:输入第一行是一个正整数 t, 表示数据组数。接下来对于每组数据,输入的第一个正整数n表示a序列的长度,随后一行有n个整数,表示a序列 。

要求输出:

输出共包含t行,每组数据输出一行。对于每组数据,如果存在这样的K,输出"YES",否则输出“NO”。

2.做法思路:通过分析题目可以知道,一个序列如果能够达成题目要求,那么这组序列最多只能包含三个数a1,a2,a3,其中两个数加上k或减去k为另外一个数,当这个序列包含的数少于三个数,那么这个序列就一定能够满足要求,无论这串序列有多长,都必须满足上述条件。

根据这个特点,我用set<long long int>类型的集合s来存储序列,它能够自动压缩这个序列,只存一次不同的数,通过判断s.size(),若s.size()==1 || s.size()==2,那么这个序列就是满足要求的序列,若果s.size()==3,那么就需要先将s中的三个数进行排序,然后判断最小数和最大数之和是否为中间数的两倍,若是,则该序列也满足条件,若不是,或者s.size()>3,那么这个序列就不满足条件。

(注意:该题的数据范围很大,不能直接用Int类型,需要用long long int!!!)

3.代码:

#include <iostream>
#include <algorithm> 
#include <cmath>
#include <set>
#include <stdio.h>
using namespace std;

long long int a[10010], num[3];
set<long long int> s;

int main()
{
    int can,j;
    int t;
    cin>>t;
    for(int i=0;i<t;i++)
    {
        int n;
        can=0, 
        j=0;
        cin>>n;
        s.clear();
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
            s.insert(a[i]);
        }
        set<long long int> ::iterator it = s.begin();
        if(s.size()==3)
        {
            while(it!=s.end())
            {
                num[j++] = *it;
                it++;
            }
            sort(num,num+3);
            if(num[0]+num[2]==2*num[1])
                can=1;
        }
        if(s.size()==1 || s.size()==2)
            cout<<"YES"<<endl;
        else if(can)    
            cout<<"YES"<<endl;
        else
            cout<<"NO"<<endl;        
    }
    return 0;
}

第二题:HRZ学英语

1.题意简介:现在给定一个字符串,字符串中包括26个大写字母和特殊字符 ‘?’ ,特殊字符 ‘?’ 可以代表任何一个大写字母。问是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果不存在,就输出-1!

说明:字典序 先按照第一个字母,以 A、B、C……Z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,SIGH 和 SIGHT),那么把短者排在前。

(注意:只输出字典序最小的!)

输入格式:输入一行,一个符合题目描述的字符串。

要求输出:如果存在这样的子串,则输出,不存在则输出-1.

2.做法思路:本题和之前做过的区间移动很类似,用一个队列类型的word来存储一个区间内的字符(共26个),并用int类型的变量unknow记录字符‘?’出现在区间内的次数,用int类型的变量have记录区间内已有的字母。

初始化时向word内存入26个字符,在存储时记录unknow和have的值,输入完毕后,调用judge()函数来判断该子串是否满足条件。

Judge()函数:首先判断have是否为26,是则该子串已经满足条件,按队列顺序输出即可;否则再判断26-have是否等于unknow,等于则说明字符‘?’可以填补为缺少的所有字母,此时按顺序输出word中的字母,遇到‘?’时替换为字典序最靠前的那个字母并输出;不等于则说明该字符串不满足条件,返回主函数,将word区间向后移动一份字符,并更新unknow和have的值,再调用judge()函数,重复执行上述区间移动操作,直到judge()函数中的子串满足条件为止。

3.代码:

#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;

char Words[26]={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
queue<char> word;
long long unknow=0;//记录'?'字符出现的次数
long long have=0;//记录当前已经得到的26个字母的个数 
bool can=false;

bool judge(int get[100])
{
    if(have==26)
    {//该区间满足条件,直接输出 
        for(int i=0;i<26;i++)
        {
            char a=word.front();
            word.pop();
            cout<<a;
        }
        can=true;
        return true;
    }
    else
    {//不满足条件,用'?'字符判断能否满足 
        int m=26-have;
        if(unknow==m)
        {//'?'字符和缺少的字符数量相等,则按条件输出 
            int j=65;
            for(j;j<=90;j++)
            {//找到第一个缺少的字母 
                if(get[j]==0)
                    break;
            }
            for(int i=0;i<26;i++)
            {
                char a=word.front();
                word.pop();
                if(a=='?')
                {//'?'字符替换为缺少的字母 
                    int b=j-65;
                    cout<<Words[b];
                    get[j]=1;
                    for(j;j<=90;j++)
                    {//找到下一个缺少的字母 
                        if(get[j]==0)
                            break;
                    }
                }
                else
                    cout<<a;
            }
            can=true;
            return true;
        }
        else//区间不满足条件
            return false; 
    }
}

int main()
{
    int get[100]={0};
    for(int i=0;i<26;i++)
    {
        char a;
        cin>>a;
        word.push(a);
        if(word.back()=='?')
            unknow++;
        else
        {
            get[word.back()]++;
            if(get[word.back()]==1)
                have++;
        }
    }
    if(judge(get))
        return 0;
    else
    {
        char b;
        while(cin>>b)
        {
            char a=word.front();
            word.pop();
            if(a=='?')
            {
                unknow--;
                word.push(b);
                if(b=='?')
                    unknow++;
                else
                    {
                    get[b]++;
                    if(get[b]==1)
                    have++;
                }
                if(judge(get))
                    break;
            }
            else
            {
                get[a]--;
                if(get[a]>1)
                {
                    word.push(b);
                    if(b=='?')
                        unknow++;
                    else
                    {
                        get[b]++;
                        if(get[b]==1)
                        have++;
                    }
                }
                else
                {
                    if(get[a]==1)
                    {
                        word.push(b);
                        if(b=='?')
                            unknow++;
                        else
                        {
                            get[b]++;
                            if(get[b]==1)
                                have++;
                        }
                        if(judge(get))
                            break;
                    }
                    if(get[a]==0)
                    {
                        have--;
                        word.push(b);
                        if(b=='?')
                            unknow++;
                        else
                        {
                            get[b]++;
                            if(get[b]==1)
                                have++;
                        }
                        if(judge(get))
                            break;
                    }
                }
            }
        }
        if(!can)
        cout<<"-1"<<endl;
    }
    return 0;
}

第三题:咕咕咚的奇妙序列

1.题意简介:112123123412345 …这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字,11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。现在想知道第 k(1<=k<=10^18) 项数字是多少。

输入格式:输入由多行组成。

第一行一个整数q表示有q组询问(1<=q<=500)

接下来第i+1行表示第i个输入ki,表示询问第ki项数字。

要求输出:输出包含q行

第i行输出对询问ki的输出结果

2.做法思路:首先观察数据范围,本题的k最大值为10^18,直接查找ki的话无疑会超时,那么就需要思考更加简化的查找方式。这里我选择的是二分的查找思想。

但是只用二分还是无法解决问题,要考虑到每个部分的数字总数,还要计算ki所在的部分,然后在这一部分中再查找ki的具体数字,这样做也会花很长时间。

另外需要注意到,该题中的数字在超出个位数后,是由多个个位数组成的,例如10这个数,在序列中由1和0组成,所以第56项为0,而不是10,这又给我计算每一个部分的数字的总数增加了难度。

在向已经AC该题目的同学“取经”后,我意识到可以不单独计算每一部分的数字总数,而是变成计算数字位数相同的“层级”的数字总数,通过分析给出的序列,可以得出一个计算规律,例如数字位数为2的层级,这一层中的数最大为99,从10到99,每一个部分中的两位数个数为1到90个,这些个数相加后的数字乘以2便是在最大数为99的部分中的层级的数字个数,然后再加上数字位数为3的层级中的两位数的部分的数字个数,就能得到2层级的数字个数总和,通过上述算法可以依次计算出1到9、10到99和100到999及其他层级的数字总数。计算完成后,根据想要求的数ki可以确定该数字所在的层数,然后通过二分来确定ki的具体数值即可。

(反思:同学提出的这个思路十分简便,但是要想在CSP模拟的有限时间内想出来对我来说还有一定难度,如果按照我一开始的思路来写代码,那么大概率会超时,因此需要在平时做题时多思考其他的思路,多思考更加简化的算法,来训练寻找更优解题思路的能力。)

3.代码:

#include <iostream>
#include <stdio.h>
#include <cmath>
using namespace std;

long long int sum(long long int x,int choose)
{
    if(choose==0)
    {
        long long int ans=0,i=1,j=1;
        for (j=1;j*10<=x;j*=10)
        {
            ans=ans+i*(j*9+1)*(j*9)/2+i*j*9*(x-j*10+1);
            i++;
        }
        long long int a=(x-j+1);
        long long int b=(a+1)*a/2;
        return ans+i*b;
    }
    if(choose==1)
    {
        long long int ans=0, i=1, j=1;
        for (j=1;j*10<=x;j*=10) 
        {
            ans+=i*j*9;
            i++;
        }
        long long int a=i*(x-j+1);
        return ans+a;
    }
}

int main()
{
    long long int n,m;
    long long int level,num,s,r,l;
    cin>>n;
    for (int i=0;i<n;i++)
    {
        cin>>m;
        l=0,r=1e9;
        while(l<=r)
        {
            long long int mid=(l+r)/2;
            if (sum(mid,0)<m)
            {
                l=mid+1;
                level=mid;
            }
            else
                r=mid-1;
        }
        m=m-sum(level++,0);
        l=0,r=level;
        while(l<=r)
        {
            long long int mid=(l+r)/2;
            if (sum(mid,1)<m)
            {
                l=mid+1;
                num=mid;
            }
            else
                r=mid-1;
        }
        m=m-sum(num++,1);
        int a[30]={0},j=0;
        while (num!=0)
        {
            a[j++]=num%10;
            num=num/10;
        }
        cout<<a[j-m]<<endl;
    }
    return 0;
}

第二次CSP反思:CSP和之前的作业不同,提交之后不会直接出结果,需要自己在本地确保无误后再提交,由于之前都是靠oj来检测自己的代码是否还有错误,所以在CSP模拟的时候,尤其是第二题,就有很多没有考虑到的地方,自己只是测试了题目中给出的例题就急急忙忙的提交了,导致最后的评测结果惨不忍睹(捂脸),所以今后在自我测试方面需要加强,最起码要将题目中涉及的所有可能都包含在代码里才有可能拿高分。

另外是时间把握的问题,平常的作业,就算对我而言比较难,也能花更多的时间去慢慢解决(熬夜党问题言论),所以平时就没有刻意去关注敲代码的时间。但是CSP模拟是限时测试,要在有限的时间内写出解决算法,还要确保错误率较低,对于我来说现在还比较头疼,所以在之后敲代码的时候要多注意把握时间,必要的时候也可以考虑使用助教说的”骗分“策略。

猜你喜欢

转载自www.cnblogs.com/muxin2333/p/12719375.html