新生训练——《愉快的假期》专场题解

前言

         应该没有很多人被搞自闭吧,而是应该愈挫愈勇的吧!算法是计算机科学领域最重要的基石之一,很多人都觉得学习最新的语言、技术、标准就是最好的铺路方法,而真正重要的是学习计算机算法和理论,因为计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论,例如数据结构、算法、编译原理、计算机体系结构、关系型数据库原理等等 。我们可以把这些基础课程比拟为“内功”,把新的语言、技术、标准比拟为“外功”。 整天赶时髦的人最后只懂得招式,没有功力,是不可能成为高手的。 尤其是算法,它是有灵魂有魅力的,我希望你们都能感受到算法的奇妙美好,虽然可能会碰到 w r o n g   a n s w e r wrong\space answer wrong answer,但只要不放弃,总结错误,认真思考,付出行动,总会 A C AC AC的,跟人生一样!

         重点: 一定要补题,可以尝试一下写 B l o g Blog Blog ,这是一个非常不错的习惯,对你的帮助很大。


A.此题只用C/C++

  • 解题思路

    模拟竖式乘法,不过我们可以做一些优化。例如是 a , b a,b a,b相乘,我们可以将 1 1 1~ 9 9 9的其中的 a a a进行运算的结果存储起来,这样就可以不必重复性的进行乘法运算了,同样对于进位处理,我们可以在乘法运算结束后再进行,这样大大的提高了计算效率。本题需要注意的就是要从低位存储到高位,避免最高位进位无法存储,同时在输出的时候要清楚前导 0 0 0

  • AC代码

  #include <bits/stdc++.h>
  
  using namespace std;
  
  const int maxn=1e3+5;
  string s1,s2;
  int len_a,len_b,a[maxn],b[maxn],result[maxn<<1],rem[10][maxn];//rem存储计算结果,利用空间换取时间。
  void solve(){
      //将0~9乘a的结果存储起来。
      for(int i=1;i<=9;i++){
          for(int j=0;j<len_a;j++){
              rem[i][j]=i*a[j];
          }
      }
      //开始模拟乘法。
      for(int i=0;i<len_b;i++){
          if(b[i]==0)continue;
          for(int k=i;k<i+len_a;k++){
                  result[k]+=rem[b[i]][k-i];
          }
      }
      //我们易知位数为len_a+len_b-1开始进位。
      int result_len=len_a+len_b-1;
      for(int i=1;i<=result_len;i++){
          result[i]+=result[i-1]/10;
          result[i-1]%=10;
      }
      //默认最高位有进位。
      result_len++;
      //清除前导0.
      while(result[result_len-1]==0&&result_len>1){
          result_len--;
      }
      //打印输出。
      for(int i=result_len-1;i>=0;i--){
          cout<<result[i];
      }
      cout<<endl;
  }
  int main(){
      while(cin>>s1>>s2){
          //进行特判。
          if(s1[0]=='0'||s2[0]=='0'){
              cout<<0<<endl;
              continue;
          }
          len_a=s1.size(),len_b=s2.size();
          //为了方便,从低位存储到高位。
          for(int i=len_a-1;i>=0;i--){
              a[(len_a-1)-i]=s1[i]-'0';
          }
          for(int i=len_b-1;i>=0;i--){
              b[(len_b-1)-i]=s2[i]-'0';
          }
          memset(result,0,sizeof(result));
          solve();
      }
      return 0;
  }

B.折线分割平面

  • 解题思路

    我们知道,如果用直线分割平面,那么由交点决定了射线和线段的条数,进而决定了新增的区域数。而用折线分割区域的话,是在原有的区域上进行增加的,即分割现有的区域。 设当有 n − 1 n-1 n1条折线的时候,区域数为 f n − 1 f_{n-1} fn1为了让区域数最多,则折线的两边的线段要和 n − 1 n-1 n1条折线的边,即 2 ( n − 1 ) 2(n-1) 2(n1)条线段相交。那么新增的线段数为 4 ( n − 1 ) 4(n-1) 4(n1),新增的射线数为 2 2 2。要注意:对于折线的相邻射线,那里只能增加一个区域。所以递推公式为

    f ( n ) = f ( n − 1 ) + 4 ( n − 1 ) + 2 − 1 f(n) =f(n-1)+4(n-1)+2-1 f(n)=f(n1)+4(n1)+21

      = f ( n − 2 ) + 4 ( n − 1 ) + 1 + 4 ( n − 2 ) + 1 \space=f(n-2)+4(n-1)+1+4(n-2)+1  =f(n2)+4(n1)+1+4(n2)+1

    . . . ... ...

      = f ( 1 ) + 4 ( n − 1 ) + 1 + 4 ( n − 2 ) + 1 + . . . + 4 × 1 + 1 \space=f(1)+4(n-1)+1+4(n-2)+1+...+4\times 1+1  =f(1)+4(n1)+1+4(n2)+1+...+4×1+1

      = 2 n 2 − n + 1 ( 等 差 数 列 求 和 公 式 ) \space=2n^2-n+1(等差数列求和公式)  =2n2n+1()

  • AC代码

  #include<bits/stdc++.h>
  
  using namespace std;
  
  int t;
  long long n;
  void solve(){
      cout<<2*n*n-n+1<<endl;
  }
  int main(){
      while(cin>>t){
          while(t--){
              cin>>n;
              solve();
          }
      }
      return 0;
  }

C.GCD LCM​

  • 解题思路

    签到题,解这道题我们需要知道 g c d gcd gcd l c m lcm lcm是什么以及它们的关系, g c d gcd gcd:最大公约数, l c m lcm lcm:最小公倍数。对于两个数 a , b a,b a,b,会有 a b / g c d = l c m ab/gcd=lcm ab/gcd=lcm,而 l c m lcm lcm自然是一定能整除 g c d gcd gcd的。 所以题目中给定 g c d gcd gcd l c m lcm lcm,我们判断无解只需要判断它们是否能够整除。而若要寻找最小的解,其最小就是小到 g c d gcd gcd本身,故此题得解。

  • AC代码

#include<bits/stdc++.h>

using namespace std;

int t;
long long g,l;
void solve(){
    if(l%g)cout<<-1<<endl;
    else cout<<g<<" "<<l<<endl;
}
int main(){
    while(cin>>t){
        while(t--){
            cin>>g>>l;
            solve();
        }
    }
    return 0;
}

D.超级楼梯

  • 解题思路

    简单的递推问题。我们思考一下,在第n层楼梯的状态 ( n ≥ 2 ) (n\geq2) (n2)是由那个得到的,是不是从 n − 1 n-1 n1层和 n − 2 n-2 n2层得到的。那么我们可以 d p [ n ] dp[n] dp[n]表示到达第 n n n层的走法,则 d p [ n ] = d p [ n − 1 ] + d p [ n − 2 ] dp[n]=dp[n-1]+dp[n-2] dp[n]=dp[n1]+dp[n2] 为了提高效率,所以我们可以进行预处理,在数据量特别大的时候这显得特别有必要。需要注意的是,第一层的走法和第二层的走法都为 1 1 1

  • AC代码

  #include<bits/stdc++.h>
  
  using namespace std;
  
  int t,n;
  int dp[42];
  void init(){
      dp[1]=1;
      for(int i=2;i<=40;i++){
          dp[i]=dp[i-1]+dp[i-2];
      }
  }
  int main(){
      init();
      while(cin>>t){
          while(cin>>n){
              cout<<dp[n]<<endl;
          }
      }
      return 0;
  }

E.Ignatius and the Princess IV

  • 解题思路

    又签到。我们可以利用容器记录出现次数,当然也可以进行排序,出现了 ( n + 1 ) / 2 (n+1)/2 (n+1)/2次的数一定是中位数,直接取中位数即可。

  • AC代码 1 1 1

  #include<bits/stdc++.h>
  
  using namespace std;
  
  unordered_map<int,int> p;
  int n,result;
  int main(){
      while(cin>>n){
          p.clear();
          int temp;
          for(int i=0;i<n;i++){
              cin>>temp;
              p[temp]++;
              if(p[temp]==(n+1)/2){
                  result=temp;
              }
          }
          cout<<result<<endl;
      }
      return 0;
  }
  • AC代码 2 2 2
  #include<bits/stdc++.h>
  
  using namespace std;
  
  int n;
  int main(){
      while(cin>>n){
          vector<int> nums(n);
          for(int i=0;i<n;i++){
              cin>>nums[i];
          }
          sort(nums.begin(),nums.end());
          cout<<nums[n/2]<<endl;
      }
      return 0;
  }

F.Supermean

  • 解题思路

    这道题超级有意思哇,不过好像没有人用第二种解法去写诶。相信细心的同学已经发现了这是一个杨辉三角,解决这道题有两种做法。第一种就是自上往下求平均数,这种方法由于除数的特殊性,为 2.0 2.0 2.0,不会造成精度损失,是可行的,但效率较低。第二种方法即是统计每个数字被加了多少次,最后除以 2 n − 1 2^{n-1} 2n1即可。 根据杨辉三角的分支可知,第 i i i个数的出现次数为 C n − 1 i C_{n-1}^i Cn1i,所以公式即为:

    a n s = ∑ i = 0 n − 1 C n − 1 i n u m s [ i ] 2 n − 1 ans=\frac{\sum_{i=0}^{n-1}C_{n-1}^inums[i]}{2^{n-1}} ans=2n1i=0n1Cn1inums[i]

    为了避免精度损失,采用对数将除法转变为减法,即 a n s = ∑ i = 0 n − 1 e l n ( C n − 1 i n u m s [ i ] ) − ( n − 1 ) l n ( 2 ) ) ans=\sum_{i=0}^{n-1}e^{ln(C_{n-1}^inums[i])-(n-1)ln(2))} ans=i=0n1eln(Cn1inums[i])(n1)ln(2))。这里还需要用到组合数利用对数转换的一个公式,即 l o g ( C n m ) = l o g ( n ( n − m ) ! ( m ) ! ) = l o g ( n ! / ( n − m ! ) ) − l o g ( m ! ) = l o g ( n ) + . . + l o g ( n − m + 1 ) − ( l o g ( 1 ) + . . . + l o g ( m ) ) log(C_{n}^{m})=log(\frac{n}{(n-m)!(m)!})=log(n!/(n-m!))-log{(m!)}=log(n)+..+log(n-m+1)-(log(1)+...+log(m)) log(Cnm)=log((nm)!(m)!n)=log(n!/(nm!))log(m!)=log(n)+..+log(nm+1)(log(1)+...+log(m)),到了这一步,这道题好做了,仔细分析代码,这道题第二种解法才是精髓。

  • AC代码 1 1 1

 #include<bits/stdc++.h>
  
  using namespace std;
  
  const int maxn=5e4+5;
  int t,n;
  //就是杨辉三角,我们可以直接模拟求最后一层。
  double nums[maxn];
  void solve(){
      while(n>1){
          for(int i=0;i<n-1;i++){
              nums[i]=(nums[i+1]+nums[i])/2.0;
          }
          n--;
      }
  }
  int main(){
      while(cin>>t){
          for(int i=1;i<=t;i++){
              cin>>n;
              for(int j=0;j<n;j++)cin>>nums[j];
              solve();
              printf("Case #%d: %.3lf\n",i,nums[0]);
          }
      }
      return 0;
  }
  • AC代码 2 2 2
  #include<bits/stdc++.h>
  
  using namespace std;
  
  int t,n;
  void solve(){
  }
  int main(){
      while(cin>>t){
          for(int k=1;k<=t;k++){
              cin>>n;
              double lnc=0;//存储我们的组合数。
              double ans=0;//结果。
              double ln2=(n-1)*log(2.0);
              double val;
              for(int i=0;i<n;i++){
                  cin>>val;
                  if(val > 0) ans += exp(log(val) + lnc - ln2);
                  else ans -= exp(log(-val) + lnc - ln2);
                  lnc = lnc + log(n-i-1) - log(i+1);//这一步是组合数的递推关键。由前一个推导后一个。而不必重复计算C
              }
              printf("Case #%d: %.3lf\n",k,ans);
          }
      }
      return 0;
  }

G.find the mincost route

  • 解题思路

    这道题超纲了,属于图论的范畴,不过有人过了,真的很棒。解法有很多,这里采用最暴力的Floyd算法。理解起来也比较简单。就是利用动态规划的思想,不断利用中转点 k k k来更新两点之间的最短花费,而根据题目的要求,我们还需要回到出发点,所以加上一条不重复的最短边回到初始位置即可。

  • AC代码

  #include<bits/stdc++.h>
  
  using namespace std;
  
  const int maxn=1e2+5;
  const int inf=1e6;
  
  int n,m;
  int graph[maxn][maxn];//无向图。
  int dis[maxn][maxn];//dis[i][j]表示i到j的最短路径。
  int minn;
  void floyd(){
      for(int k=1;k<=n;k++){
          for(int i=1;i<k;i++){
              for(int j=i+1;j<k;j++){
                  minn=min(minn,dis[i][j]+graph[j][k]+graph[k][i]);
              }
          }
          for(int i=1;i<=n;i++){
              for(int j=1;j<=n;j++){
                  dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
              }
          }
      }
  }
  int main(){
      while(cin>>n>>m){
          //初始化
          for(int i=1;i<=n;i++){
              for(int j=1;j<=i;j++){
                  dis[i][j]=dis[j][i]=graph[i][j]=graph[j][i]=inf;
              }
          }
          minn=inf;
          int u,v,w;
          for(int i=0;i<m;i++){
              cin>>u>>v>>w;
              if(w<graph[u][v])graph[u][v]=graph[v][u]=dis[u][v]=dis[v][u]=w;
          }
          floyd();
          if(minn==inf)cout<<"It's impossible.\n";
          else cout<<minn<<endl;
      }
      return 0;
  }

H.Add All

  • 解题思路

    这道题其实特别简单呀,不过过的人特别少,千万不要因为在 H H H题而不敢做,因为有的出题人他偏不按题目难易来排序的。

    回到题目,这就是一道纯粹的贪心题,我们总是要进行 n − 1 n-1 n1加法,那么我们希望进行的成本最小,那么必然大的数放到后面来加,将最小的两个数优先计算。不断重复这种操作,这里要利用一个 S T L STL STL容器:优先队列。 S T L STL STL还不会的建议大家去学,这有妙用。

  • AC代码

#include<bits/stdc++.h>
  
  using namespace std;
  
  //一道纯粹的贪心题,这里需要借助优先队列来实现模拟贪心。
  int n;
  priority_queue<int,vector<int>,greater<int> > q;//优先队列。默认大顶堆,这里需要使用小顶堆。 
  void solve(){
      //模拟加法。当只有一个元素的时候则退出。
      int ans=0;//计算成本。
      int temp;
      while(q.size()>1){
          temp=q.top();
          q.pop();
          temp+=q.top();
          q.pop();
          ans+=temp;
          q.push(temp);
      }
      cout<<ans<<endl;
  }
  int main(){
      while(cin>>n&&n!=0){
          int temp;
          while(!q.empty())q.pop();
          for(int i=0;i<n;i++){
              cin>>temp;
              q.push(temp);
          }
          solve();
      }
      return 0;
  }

I.Prime Distance

  • 解题思路

    算是这场最难的一道题吧,没有做过这种类型的确实很难想到。此题需要有素数筛的基础。我们来分析这道题,需要我们将给定区间的素数进行计算出相邻差最小以及相邻差最大的素数。那么我们肯定是想到将这个区间中的素数筛出来,再进行遍历求解。而给定的数据范围是 ( 1 ≤ L < U ≤ 2 , 147 , 483 , 647 ) (1\leq L< U\leq 2,147,483,647) (1L<U2,147,483,647),我们根本开不了这么大的数组,而且就算可以也明显会 T L E TLE TLE,那么我们该怎么办呢?对于给定的区间长度不会超过 1 e 6 1e6 1e6,而我们素数筛的核心就是将 s q r t ( n ) sqrt(n) sqrt(n)以内的素数倍数全部排除,那么我们先求出 s q r t ( 2 , 147 , 483 , 647 ) sqrt(2,147,483,647) sqrt(2,147,483,647) 的素数,求解完之后那么我们将可以直接筛选这段区间的素数,并将区间移动从 [ l , r ] [l,r] [l,r] [ 0 , r − 1 ] [0,r-1] [0,r1],这里需要做的处理就是在筛素数的时候将这段区间的数减去 l l l。具体看 A C AC AC代码。这道题非常重要,还请各位一定要补题。

  • AC代码

  #include<cstdio>
  #include<iostream>
  #include<cmath>
  #include<cstring>
  
  using namespace std;
  
  const int maxn=1e6+7;
  bool notprimer[maxn];
  long long primer[maxn];
  bool Interval_primer[maxn*10];//即区间l~r的素数,我们将其放小,即0~r-l。
  long long l,r;//区间[l,r]。
  int cnt=0;
  void init(int maxn){
      //将素数筛出。
      for(int i=2;i<=maxn;i++){
          if(!notprimer[i]){
              primer[cnt++]=i;
              for(int j=i*i;j<=maxn;j+=i){
                  notprimer[i]=true;
              }
          }
      }
  }
  void solve(){
  }
  int main(){
      init(5e4);//将素数筛除,再利用素数筛的思想。
      while(cin>>l>>r){
          memset(Interval_primer,true,sizeof(Interval_primer));//一开始默认这区间的数都是素数。
          if(l==1)l++;//避免1的干扰。
          for(int i=0;i<cnt;i++){
              long long temp1=(l-1)/primer[i]+1;//这里为了确保temp1*primer[i]要大于l.找出区间的最小倍数因子。当然最小为2.
              if(temp1==1)temp1++;
              long long temp2=r/primer[i];//找出区间的最大倍数因子。
              for(long long j=temp1;j<=temp2;j++)Interval_primer[j*primer[i]-l]=false;
          }
          //筛完之后就可以开始遍历了。
          long long temp=-1,min_l,min_r,max_l,max_r,maxx=-1,minn=1e9;//其中temp代表前一个素数。
          for(long long i=0;i<=r-l;i++){
              if(Interval_primer[i]){
                  if(temp==-1){
                      temp=i;
                      continue;
                  }
                  if(maxx<i-temp){
                      //更新。
                      maxx=i-temp;
                      max_l=temp+l;
                      max_r=i+l;
                  }
                  if(minn>i-temp){
                      minn=i-temp;
                      min_l=temp+l;
                      min_r=i+l;
                  }
                  temp=i;//更新上一个素数。
              }
          }
          if(maxx==-1){
              //说明不存在两个素数以上。
              printf("There are no adjacent primes.\n");
          }
          else{
              printf("%lld,%lld are closest, %lld,%lld are most distant.\n",min_l,min_r,max_l,max_r);
          }
      }
      return 0;
  }

J.Polycarp’s New Job

  • 解题思路

    这道题没人过让我有点失望,是题目意思没搞懂吗?英文题面的题不要怕,实在看不懂可以用翻译嘛!。其实就是维护一个最大长方形就可以

  • AC代码

  #include<bits/stdc++.h>
  
  using namespace std;
  
  //维护一个最大长方形即可。
  int n,maxx[2],temp[2];
  char op[2];
  int main(){
      while(scanf("%d",&n)!=EOF){
          maxx[0]=maxx[1]=0;//初始无长方形。
          for(int i=0;i<n;i++){
              scanf("%s%d%d",op,&temp[0],&temp[1]);
              sort(maxx,maxx+2),sort(temp,temp+2);//默认小的做宽,大的做高。
              if(op[0]=='+'){
                  //更新最大长方形。
                  maxx[0]=max(maxx[0],temp[0]);
                  maxx[1]=max(maxx[1],temp[1]);
              }
              else{
                  //判断是否可行。
                  if(maxx[0]<=temp[0]&&maxx[1]<=temp[1]){
                      puts("YES");
                  }
                  else{
                      puts("NO");
                  }
              }
          }
      }
      return 0;
  }
posted @   unique_pursuit  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示