天元公学-2023届信奥期末考题解

\(2023\) 届信奥期末考题解

\(T1\ Ranking\)

  • 简单题。考察算法范围:分支结构。

  • 分析题意,可得:

    • \(a+b+c\le 19\),输出 NO

    • \(a+b+c>19\),输出 YES

  • 因为对于 \(100\%\) 的数据,\(1\le a,b,c\le 10^9\),此题需要计算 \(a+b+c\)

  • \(a+b+c\le 3\times 10^9\),而 \(int\) 的最大范围约为 \(2\times 10^9\),所以要开 \(\operatorname{long}\ \operatorname{long}\)

  • 时间复杂度为 \(O(1)\)

  • //code by sjh
    long long a,b,c;
    cin>>a>>b>>c;
    if (a+b+c<=19) printf("NO\n");
    else printf("YES\n");
    

\(T2\ a\div b\ problem\)

  • 简单题。考查知识范围:字符数组,循环结构。

  • 首先输入一个字符串。

  • 接着输入 \(m\) 个数。

    • 我们在每次输入后进行相应的修改操作 a[x]=y
  • 时间复杂度 \(O(m)\)

  • \(\operatorname{char}\) 数组写法。

  • //code by yyyx
    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    char a[1000005];
    int main()
    {
        scanf("%d %d %s",&n,&m,a);
        while(m--)
        {
            int x; char y;
            scanf("%d %c",&x,&y);
            a[x]=y;
        }
        printf("%s",a);
    
        return 0;
    }
    
  • \(\operatorname{string}\) 写法。

  • //code bt sjh
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        int n,m;
        scanf("%d %d",&n,&m);
        string s;
        cin>>s;
        while (m--){
            int x;
            char y;
            scanf("%d %c",&x,&y);
            s[x]=y;
        }
        cout<<s<<endl;
        return 0;
    }
    

\(T3\ Positive \ Integer\)

  • 简单题,需要一个优化。考察算法范围:选择结构,循环结构

  • 分析题意,有一个很显然的枚举写法。

  • int n,k,ans=0;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=pow(n,n);++i){
        if (i%10==n && (i%k)%n==0){
            ans++;
        }
    }
    printf("%d",ans);
    
  • 对于以上代码,可以获得 \(56\) 分的成绩(出题人还是非常良心的)。

  • 时间复杂度为 \(O(9^9)\),对于这么庞大复杂度,程序在 \(1s\) 内跑不完,所以需要优化。

  • 我们观察发现,如果 \(x\) 的个位是 \(n\),那么满足这个条件的相邻两个数一定相差 \(10\),而第一个满足此条件的数就是 \(n\) 本身,所以我们可以写出下列代码。

  • //code by sjh
    int n,k,ans=0;
    scanf("%d %d",&n,&k);
    int t=pow(n,n);
    for(int i=n;i<=t;i=i+10){
        if ((i%k)%n==0){
            ans++;
        }
    }
    printf("%d\n",ans);
    
  • 此代码的时间复杂度为 \(O(\frac{9^9}{10})\),此复杂度可以通过。

\(T4\ a-b\ problem\)

  • 思维题。考察算法范围:选择结构,思维。

  • 如果直接用 \(\operatorname{long}\ \operatorname{long}\) 相减,你可以得到 \(58\) 分的成绩。

  • 于是我们观察数据范围。发现 \(-2^{63} \le a,b \le 2^{63}-1\),在这种情况下,\(a-b\) 有可能达到 \(-2^{64}+1\) 或者 \(2^{64}-1\) 的情况,所以我们不能使用 \(\operatorname{long}\ \operatorname{long}\)

  • 在这里,我们需要引入一个新的变量类型,叫做 \(\operatorname{unsigned}\ \operatorname{long}\ \operatorname{long}\)\(\operatorname{unsigned}\ \operatorname{long}\ \operatorname{long}\) 的规模是 \([0,2^{64}-1]\),。

  • 我们引入 \(\operatorname{unsigned}\ \operatorname{long}\ \operatorname{long}\) 后,需要进行分类讨论。

    • 如果 \(a<b\)

      • 如果 \(b>0\),则输出 \(-(b-a)\)

      • 如果 \(b\le0\),则将 \(\operatorname{unsigned}\ \operatorname{long}\ \operatorname{long}\ t\) 赋值成 \(b\),输出 \(-(t-a)\)

    • 如果 \(a\ge b\)

      • 如果 \(a<0\),则输出 \(a-b\)

      • 如果 \(a\ge 0\),则将 \(\operatorname{unsigned}\ \operatorname{long}\ \operatorname{long}\ t\) 赋值成 \(a\),输出 \((t-b)\)

  • //code by yyyx
    #include<bits/stdc++.h>
    using namespace std;
    long long a,b;
    unsigned long long t;
    int main(){
        scanf("%lld %lld",&a,&b);
          if(a<b){
                if(b<0) printf("-%lld",b-a);
                else t=b,printf("-%llu",t-a);
          }
          else {
                if(a<0) printf("%lld",a-b);
                else t=a,printf("%llu",t-b);
          }
          return 0;
    }
    
  • 当然,你也可以用 \(\operatorname{int128}\) 或者高精度来实现。

  • \(\operatorname{int128}\) 代码(超纲了)

  • //code by sjh
    #include <bits/stdc++.h>
    using namespace std;
    inline __int128 read(){
        __int128 x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9'){
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';
            ch=getchar();
        }
        return x*f;
    }
    inline void print(__int128 x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>9)
            print(x/10);
        putchar(x%10+'0');
    }
    int main(void){
        __int128 a=read();
        __int128 b=read();
        print(a-b);
        return 0;
    }
    

\(T5\ a+ b\ problem\)

  • 模拟题。考察算法范围:选择结构,循环结构

  • 我们只需要根据题目的描述来模拟即可。

    • 如果有 \(+-\times\div\) 连在一起,则为 wrong question

    • 如果 \(+-\times\div\) 中有一个在最后一位,则为wrong question

    • 如果不为 wrong question,且存在 \(\times\) \(\div\),则称为 challenging questio

    • 否则为 water question

  • //code by yyyx
    #include<bits/stdc++.h>
    #define wa "water question"
    #define cq "challenging question"
    #define wq "wrong question"
    using namespace std;
    int n,ck;
    bool op;
    char a[1005];
    int main()
    {
        scanf("%d",&n);
        while(n--)
        {
            scanf("%s",a); op=ck=0;
            for(int i=0;i<strlen(a);i++)
            {
                if(a[i]<='9'&&a[i]>='0') op=0;
                else
                {
                    if(op==1)
                    {
                        ck=-1;
                        break;
                    }
                    op=1;
                    if(a[i]=='*'||a[i]=='/') ck=1;
                }
            }
            if(op==1||ck==-1) puts(wq);
            else if(ck==1) puts(cq);
            else if(ck==0) puts(wa);
        }
    
        return 0;
    }
    
  • //code by sjh
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        int n;
        cin>>n;
        while (n--){
            int f1=0,f2=0;
            string s;
            cin>>s;
            for(int i=0;i<s.size();++i){
                if (s[i]=='*'||s[i]=='/') f1=1;
                if (i!=s.size()-1&&s[i]<'0'&&s[i+1]<'0') f2=1;
                if (i==s.size()-1&&s[i]<'0') f1=1;
            }
            if (f2==1) printf("wrong question\n");
            else if (f1==1) printf("challenging question\n");
            else printf("water question\n");
        }
        return 0;
    }
    

\(T6\) \(Infinite\ Motion\ of\ Points\)

  • 数学规律题。考察算法范围:选择结构,循环结构

  • 此题的关键在于找规律,首先,我们先从部分分讲起。

  • 对于前 \(60\%\) 的数据,我们都可以通过暴力枚举点的每一次运动实现。

  • 如果我们不通过暴力,而是手动推演点的每一次运动过程。

    • 对于前 \(10\%\) 的数据,观察样例即可观察出来。

    • 对于前 \(20\%\) 的数据,我们可以用 \(if\) 每个判断过去。

    • 通过手动模拟 \(1 \le n \le 10\) 的数据,我们可以发现点的运动为 \(6\) 个一循环。

    • 所以我们只需要判断 \(n\)\(6\) 的模数就好了。

  • //code by sjh
    #include <bits/stdc++.h>
    using namespace std;
    int f[10][2]={{0,3},{3,0},{7,4},{8,3},{5,0},{1,4}},T;
    int main(){
        long long n;
        scanf("%lld %d",&n,&T);
          printf("%d %d\n",f[n%6][0],f[n%6][1]);
          while (T--){
                scanf("%lld",&n);
                printf("%d %d\n",f[n%6][0],f[n%6][1]);
          }
        return 0;
    }
    
  • 时间复杂度 \(O(T)\),轻松通过。

\(T7\ Best\ Position\)

  • 模拟题,按照题目模拟即可。

  • 首先我们要找到两个 \(n\) 的位置。

  • 接着,我们依次枚举所有位置,如果这个位置包含以下条件,则为最佳位置。

    • 这个座位为 \(0\)

    • 这个座位到两个 \(2\) 的距离相等。

    • 这个座位到两个 \(2\) 的距离比之前确定的最佳位置的距离要大。

  • 时间复杂度:\(O(nm)\),需要一些常数优化使得代码通过 \(n,m=5000\) 的数据点。

  • 其他细节见代码。

  • //code by sjh
    #include <bits/stdc++.h>
    using namespace std;
    const int N=5e3+5;
    int a[N][N],maxdistance;
    struct node{//这里为结构体,超纲了,也可以用变量记录
        int x,y;
    }p[3];
    inline int read(){//这里是快读,比scanf要快
        int x=0,f=1;
        int ch=getchar();
        while(ch<'0'||ch>'9'){
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    int main(){
        int n,m,flag=0,fx,fy;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                a[i][j]=read();
                if (flag&&a[i][j]==2){
                    p[2].x=i,p[2].y=j;
                }
                if (!flag&&a[i][j]==2){
                    p[1].x=i,p[1].y=j;
                    flag=1;
                }
            }
        }
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                if (a[i][j]==0){
                    int t1=abs(i-p[1].x),t2=abs(j-p[1].y),t3=abs(i-p[2].x),t4=abs(j-p[2].y);
                    if (t1*t1+t2*t2==t3*t3+t4*t4){
                        if (t1*t1+t2*t2>maxdistance){
                            maxdistance=t1*t1+t2*t2;
                            fx=i,fy=j;
                        }
                    }
                }
            }
        }
        if (!maxdistance) printf("No solution\n");
        else printf("%d %d",fx,fy);
        return 0;
    }
    

\(T8\ Picking\ Ganoderma\ lucidum\)

知识点

贪心,二分

思路 \(1\)

  • 二分求最低海拔 \(mid\) ,表示海拔高度 \(> mid\) 的灵芝都被采集。
  • 例如第一组样例中,二分最后结果为 \(99\),因为海拔高度 \(> 99\) 的灵芝数量为 \(4\) ,而此时还剩下 \(1\) 个未采集,则这 \(1\) 个会选取海拔为 \(99\) 的灵芝,总攀登历程为 \(102+101+100+100+99=502\)
  • 时间复杂度为 \(O(n\ log\sum_{i=1}^n max(h_i))\)
//code by yyyx
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int n, k;
ll h[1000005], ans;
int main()
{
    scanf("%d %d", &n, &k);
    for (int i = 0; i < n; i++)
        scanf("%d", &h[i]);
    auto check = [=](int height) -> bool
    {
        ll cnt = 0;
        for (int i = 0; i < n; i++)
            cnt += max(0ll, h[i] - height);
        return cnt <= k;
    };
    int l = 0, r = 2e9, mid;
    while (l < r)
	{
        mid = l + (r - l >> 1);
        if(check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    for (int i = 0; i < n; i++)
    {
		if(h[i] > r)
		{
            ans += (0ll + h[i] + r + 1) * (h[i] - r) >> 1;
            k -= h[i] - r;
        }
	}
    ans += 1ll * k * r;
    printf("%lld", ans);

    return 0;
}

思路 \(2\)

贪心做法

  • 将原序列从大往小排序,贪心从大往小拿,对于 \(h_i\) 来说,目前 \([h_{i+1}+1,h_i]\) 这个区间的海拔高度的灵艺没拿,共有 \(i\times (hi+1)\) 个,令其为 \(cnt_i\)
  • \(cnt_i < k\),则 \(ans+=(h[i]+h[i+1]+1)\times (h[i]-h[i+1])\div 2 \times i\)
  • 反之,令 \(x=k\div i\),\(y=k%i\) ,则 \(ans+=(h[i]+h[i]-x+1)\times cnt\div 2 \times i+y\times (h[i]-x)\)。每次取完后,\(k\) 会减少相应的数量。
  • 时间复杂度为 \(O(n\operatorname{log}n)\)
//code by yyyx
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int n, k;
ll h[1000005], ans, cnt, x, y;
int main()
{
    scanf("%d %d", &n, &k);
    for (int i = 0; i < n; i++)
        scanf("%d", &h[i]);
    sort(h, h + n, greater<ll>());
    for (int i = 0; i < n; i++)
    {
        cnt = (h[i] - h[i + 1]) * (i + 1);
        if(cnt <= k)
		{
            ans += (h[i] + h[i + 1] + 1) * (h[i] - h[i + 1]) * (i + 1) >> 1;
            k -= cnt;
        }
		else
		{
            x = k / (i + 1);
            y = k % (i + 1);
            ans += (h[i] + h[i] - x + 1) * x * (i + 1) >> 1;
            ans += y * (h[i] - x);
            k = 0;
        }
	}
    printf("%lld", ans);

    return 0;
}

\(T9\ Series\)

  • 数论题,超纲。考察算法范围:数论

  • 首先我们先观察数据范围,发现当 \(1\le k \le 14\) 时,直接暴力枚举即可。

  • double k,c=0;
    cin>>k; 
    for(int i=1;;++i){
        c=c+1.0/i;
        if (c>k){
            cout<<i<<endl;
            return 0;
        }
    }
    
  • 此代码可以通过 \(70\) 分,主要是出题人多放了一个点。

  • 时间复杂度为 \(O(e^k)\)\(e\) 指自然常数。

  • 正解,数论(调和级数)

  • 已知 \(S_n=1+\frac{1}{2}+\frac{1}{3}+…+\frac{1}{n}= {\textstyle \sum_{k=1}^{n}} \frac{1}{k}\)

  • 很明显得,\(S_n\) 为第 \(n\) 个调和级数。

  • 欧拉推导过求调和级数有限多项和的表达式:

    • \({\textstyle \sum_{k=1}^{n}} \frac{1}{k}=\operatorname{ln}(n+1)+γ\)

    • \(γ≈0.577215664901532860606512090082402431042159335\)

    • 我们直接根据拿来用即可。

  • 我们需要满足 \(S_n>k\),即满足 \(\operatorname{ln}(n+1)+γ>k\),化简为 \(n>e^{k-γ}-1\)

  • 故,我们只需要求出满足上市的最小的 \(n\),所以 \(n=e^{k-γ}+0.5(四舍五入)\)

  • 时间复杂度约为 \(O(1)\)

  • //code by sjh
    #include<bits/stdc++.h>
    using namespace std;
    double G=0.577215664901532860606512090082402431042159335;
    int main(){
        long long k,n;
        cin>>k;
        n=exp(k-G)+0.5;
        printf("%lld",n);
        return 0;
    }
    
  • 当然如果你有坚持不懈的精神,你也可以写成这样。

  • //code by st
    #include <bits/stdc++.h>
    using namespace std;
    long long a[30]={0,2,4,11,31,83,227,616,1674,4550,12367,33617,91380,248397,675214,1835421,4989191,13562027,36865412,100210581,272400600,740461601,2012783315,5471312310,14872568831,40427833596};
    int main(){
        int n;
        cin>>n;
        cout<<a[n];
        return 0;
    }
    

\(T10\ Sounder\)

  • 特别鸣谢 \(\operatorname{So\_noSlack}\)\(\operatorname{xxxalq}\) 的数据支持。

  • 普及算法综合题。考察算法范围:二分,二维差分,二维前缀和。

  • 对于前 \(40\) 分的数据,直接暴力枚举即可

  • // 伪代码
    for(int i=0;;++i){//枚举可不可以
        for(int j=1;j<=s;++j){
            for(int k=1;k<=s;++k){
               if (a[j][k]==1){//如果是宿管寝室,对于范围内的所有都标记
                    for(){
                        for(){
                            //标记
                        }
                    }
               }
            }
        }
        // 如果所有-1都被覆盖,则输出
    }
    
  • 满分做法

    • 看到求最值,并且发现这道题的答案很明显有单调性,于是果断选择二分答案。

    • 在二分内部,可以通过二维差分 \(+\) 二维前缀和的方式,用 \(O(s^2)\) 的时间完成对数组的标记。

    • 判断,若所有学生寝室都被覆盖掉,则此为可行方案。

    • 时间复杂度 \(O(s^2\operatorname{log} s)\),空间复杂度 \(O(s^2)\)

    • 此题有 \(s=10^6\) 的做法,有兴趣的同学可以尝试。

  • 详细思路见关键代码。

  • //code by sjh
    int check(int r){
        memset(b,0,sizeof(b));
        if (r!=0){
            for(int i=1;i<=s;++i){//二维差分
                for(int j=1;j<=s;++j){
                    if (a[i][j]==1){
                        b[max(i-r,1)][max(j-r,1)]++;//zuoshang
                        b[min(s,i+r)+1][max(1,j-r)]--;//youshang
                        b[max(1,i-r)][min(s,j+r)+1]--;//zuoxia
                        b[min(i+r,s)+1][min(j+r,s)+1]++;//youxia
                    }
                }
            }
        }
        for(int i=1;i<=s+1;++i){//二维前缀和
            for(int j=1;j<=s+1;++j){
                b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+b[i][j];
            }
        }
        for(int i=1;i<=s;++i){
            for(int j=1;j<=s;++j){
                if (a[i][j]==-1&&b[i][j]==0){
                    return 1;
                }
            }
        }
        return 0;
    }
    
  • //code by sjh
    //主函数
    scanf("%d %d %d",&n,&m,&s);
    for(int i=1;i<=n;++i){
        int x,y;
        scanf("%d %d",&x,&y);
        a[x][y]=-1;
    }
    for(int i=1;i<=m;++i){
        int x,y;
        scanf("%d %d",&x,&y);
        a[x][y]=1;
    }
    int l=0,r=1001;
    while (l<=r){//二分
        int mid=(l+r)/2;
        if (check(mid)==1){
            l=mid+1;
        }
        else{
            ans=mid;
            r=mid-1;
        }
    }
    printf("%d",ans);
    return 0;
    
posted @ 2024-01-05 19:45  Jasonshan10  阅读(163)  评论(1)    收藏  举报