2020年常熟理工学院第一届线上ACM选拔赛热身赛解题报告

2020年常熟理工学院第一届线上ACM选拔赛热身赛-by bob_teacher

热身赛大多数题目为原题,此博客原文地址:https://www.cnblogs.com/BobHuang/p/12590974.html

1.5619: Kannyi的数字密码

这是一个模拟题,我们需要把题目条件组合起来。而且Kannyi数也不多,我们可以分步实现。

  1. 循环遍历所有数字

  2. 计算当前数的数字和,他的因子是2到i

  3. 找到因子相减并除掉所有因子,即分解质因数

  4. 是素数continue

  5. 是Smith数且不是素数进行统计

  6. 到第num个进行输出

#include <bits/stdc++.h>
using namespace std;
//计算当前数字的每一位之和
int sum_of_digit(int n)
{
    int sum = 0;
    //按照进制转换拿到每一位相加
    while (n)
         sum += n % 10, n /= 10;
    return sum;
}
int main()
{
    int n;
    //读入n,读到EOF的返回值为0,结束循环
    while (cin >> n)
    {
         //num用来统计第几个数
         int num = 0;
         for (int i = 0;; i++)
         {
              int sum = sum_of_digit(i), t = i;
              for (int j = 2; j < i; j++)
              {
                   //是因子
                   if (t % j == 0)
                   {
                        //除掉所有因子
                        while (t % j == 0)
                        {
                             //减去j的每一位之和
                             sum -= sum_of_digit(j);
                             t /= j;
                        }
                   }
              }
              //是素数
              if (t == i || i == 2)
                   continue;
              //是Smith数
              if (sum == 0)
                   num++;
              //是第n个进行输出
              if (num == n)
              {
                   cout << i << "\n";
                   break;
              }
         }
    }
}

当然你也可以直接计算出所有的Kannyi数,也就是本地打表

#include <bits/stdc++.h>
using namespace std;
int num[] = {0, 4, 22, 27, 58, 85, 94, 121,
166, 202, 265, 274, 319, 346, 355, 378, 382,
391, 438, 454, 483, 517, 526, 535, 562, 576,
588, 627, 634, 636, 645, 648, 654, 663, 666,
690, 706, 728, 729, 762, 778, 825, 852, 861,
895, 913, 915, 922, 958, 985, 1086, 1111, 1165};
int main()
{
    int a;
    while (~scanf("%d", &a))
    {
         printf("%d\n", num[a]);
    }
    return 0;
}

2.5266: 三角形相似

三角形相似即各边对应成比列,我们需要对他们进行排序然后两两比较,如果你要用除法记得强制转换

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int a[3], b[3];
    while (cin >> a[0] >> a[1] >> a[2])
    {
         cin >> b[0] >> b[1] >> b[2];
         sort(a, a + 3);
         sort(b, b + 3);
         //两两进行比较,除法可以转换为乘法
         if (a[0] * b[1] == a[1] * b[0] &&
              a[0] * b[2] == b[0] * a[2] &&
              a[1] * b[2] == b[1] * a[2])
              printf("Yes\n");
         else
              printf("No\n");
    }
}

3.5127: 糖果王子

我们很容易想到用循环去解决,即遍历10岁到30岁,符合输出

#include <stdio.h>
int main()
{
    int m, n, q, i, f;
    while (~scanf("%d%d", &m, &n))
    {
         //转换为角
         m = m * 10;
         //标记还未输出
         f = 1;
         //只访问年龄
         for (i = 10; i <= 30; i++)
         {
              //拥有的钱数
              q = m * i;
              //选两角的,比他大
              if (q >= 2 * n)
              {
                   //输出并标记
                   printf("%d\n", i);
                   f = 0;
                   break;
              }
         }
         //没有标记为Sorry
         if (f)
              printf("Sorry\n");
    }
    return 0;
}

这是一个比较简单的数学问题,都买2角的,也就是m * 10 / 2 = m* 5,不能整除说明有余数,还要大一岁。但是小于10岁,到十岁一定可以买得起。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int a, b, c;
    while (cin >> a >> b)
    {
         //1年可以买到a个糖果
         a = a * 5;
         //需要c年
         c = b / a;
         //有余数进行加法计算
         if (b % a != 0)
              c++;
         if (c < 10)
              c = 10;
         if (c >= 10 && c <= 30)
              cout << c << endl;
         else
              cout << "Sorry" << endl;
    }
    return 0;
}

4.5264: 买张公交卡吧

这个题目其实只需要标记上一次坐公交车的时间以及是否可以使用换乘优惠就好。

#include <stdio.h>
int main()
{
    int t;
    while (~scanf("%d", &t))
    {
         int h, m, s;
         //f用来标记能不能有换乘优惠,初始不能
         //sum用来记录花的钱
         int sum = 0, f = 1, t2 = 0;
         while (t--)
         {
              scanf("%d:%d:%d", &h, &m, &s);
              //进行进制转换,得到是今天的
              int t1 = h * 3600 + m * 60 + s;
              //时间在1H内,且可以使用换乘优惠
              if (t1 - t2 <= 3600 && f == 0)
              {
                   f = 1;
              }
              else
              {
                   //没有优惠,那么下次具有优惠
                   sum += 2;
                   f = 0;
              }
              //记录上一次的上车时间
              t2 = t1;
         }
         //输出钱数
         printf("%.1f\n", sum * 0.6);
    }
    return 0;
}

5.1265: 坐标变换

这是个几何问题,我们可以把角度转换为弧度,然后进行三角变换

#include <stdio.h>
#include <math.h>
int main()
{
    double x, y, x2, y2, c2, c;
    double PI=acos(-1.);
    while (~scanf("%lf%lf%lf%lf%lf", &x, &y, &x2, &y2, &c2))
    {
         //对应的弧度角
         c = c2 / 180 * PI;
         //进行三角变换
         double ansx = x + (x2 - x) * cos(c) - (y2 - y) * sin(c);
         double ansy = y + (y2 - y) * cos(c) + (x2 - x) * sin(c);
         printf("%.1f %.1f\n", ansx, ansy);
    }

    return 0;
}

6.5623: Alice找三角形

这个题目可以直接排序,然后选取相邻的三条边
大于20必存在三角形,因为形不成三角形就是斐波那契数列,第21项超过了10000。所以乱搞的做法也能过

#include<bits/stdc++.h>
using namespace std;

int main()
{
   int n,a[1005];
   while(scanf("%d",&n)!=EOF)
   {
       for(int i=0;i<n;i++)
           scanf("%d",&a[i]);
       sort(a,a+n);
       int maxx=-1;
       for(int i=2;i<n;i++)
           if(a[i]<a[i-1]+a[i-2])
           {
               maxx=max(maxx,a[i]+a[i-1]+a[i-2]);
           }
       printf("%d\n",maxx);
   }
   return 0;
}

强行乱搞,及时结束循环不会超时

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

bool cmp(int a, int b)
{
   return a>b;
}

int solve(int a[], int n)
{
   for(int i=0;i<n;i++)
   {
       for(int j=i+1;j<n;j++)
       {
           for(int k=j+1;k<n;k++)
           {
               if(a[i]<a[j]+a[k])
                   return a[i]+a[j]+a[k];
           }
       }
   }
   return -1;
}

int main()
{
   int n, a[1000];
   while(scanf("%d", &n)!=EOF)
   {
       for(int i=0;i<n;i++)
           scanf("%d", &a[i]);
       sort(a, a+n, cmp);
       int res = solve(a, n);
       printf("%d\n", res);
   }
   return 0;
}

出题人曾经的做法,去二分

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N];
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n;
    while (cin >> n)
    {
         int a[1005];
         for (int i = 0; i < n; i++)cin >> a[i];
         sort(a, a + n);
         int ans = -1;
         for (int i = 0; i < n; i++)
              for (int j = i + 1; j < n; j++)
              {
                   int y = a[j] + a[i];
                   //找到第一个大于等于两边之和的第一个位置
                   int pos = lower_bound(a + j + 1, a + n, y) - a;
                   //还是这条边continue
                   if (pos - 1 == j)
                        continue;
                   //更新答案
                   ans = max(ans, a[i] + a[j] + a[pos - 1]);
              }
         cout << ans << "\n";
    }
    return 0;
}

7.5761: 最少步数

比较经典的广搜,广搜可以保证在当前层大家需要的步数一样,我们可以带上步数去广度优先搜索

#include <bits/stdc++.h>
using namespace std;
//共有12个方向,日有8个,田有4个
int dir[12][2] = {
    1, 2, 1, -2, -1, 2, -1, -2, -2, 1, -2, -1,
    2, 2, 2, -2, -2, 2, -2, -2};
struct T
{
    int x, y, step;
};
bool vis[105][105];
void bfs(int x, int y)
{
    //清空标记
    memset(vis, 0, sizeof(vis));
    queue<T> Q;
    Q.push({x, y, 0});
    vis[x][y] = 1;
    while (!Q.empty())
    {
         T X = Q.front();
         Q.pop();
         for (int i = 0; i < 12; i++)
         {
              int x = X.x + dir[i][0];
              int y = X.y + dir[i][1];
              //越界或者访问过不访问
              if (x <= 0 || y <= 0 || x > 100 || y > 100 || vis[x][y])
                   continue;
              //到目的地
              if (x == 1 && y == 1)
              {
                   cout << X.step + 1 << "\n";
                   return;
              }
              Q.push({x, y, X.step + 1});
              //第一次最少步数,之后不再访问
              vis[x][y] = 1;
         }
    }
}
int main()
{
    int x, y;
    cin >> x >> y;
    bfs(x, y);
    cin >> x >> y;
    bfs(x, y);
    return 0;
}

8.1219: 数据结构练习题――括号匹配

括号栈,我们可以用栈进行存储。

#include <stdio.h>
#include <stack>
using namespace std;
int main()
{
    int t;
    scanf("%d", &t);
    //吃掉t后面的换行符
    getchar();
    while (t--)
    {
         char c;
         //设置一个栈存储括号
         stack<char> s;
         //读取一行,即到换行符结束
         while (c = getchar(), c != '\n')
         {
              //如果当前不空,且括号可以匹配
              if (!s.empty() &&
              (s.top() == '{' && c == '}' ||
               s.top() == '[' && c == ']' ||
               s.top() == '(' && c == ')' ))
                   s.pop();
              //为括号
              else if (c == '}' || c == '{' || c == ']' || c == '[' || c == ')' || c == '(')
                   s.push(c);
         }
         //栈为空
         if (s.empty())
              printf("match\n");
         //栈里还有很多
         else if (s.size() > 1)
              printf("wrong\n");
         //最上面是左括号
         else if (s.top() == '{' || s.top() == '(' || s.top() == '[')
              printf("miss right\n");
         else
              printf("miss left\n");
    }
    return 0;
}

9.5505: Alice与圆周率π

其实就是个期望问题,后来经过测试才发现了OJ的RAND_MAX为32767,我们把它对20取余,前7个数被取到的次数会大些。但是到总次数就是不影响了,如果你恰巧%20000/1000.0,你的期望就会差很多。
对20取余怎么想的呢,比如区间[0,20],去掉20这个点,其实问题是不大的,因为他们之间有无数个点,假如我们可以均匀得随机一个x在[0,20),并对他们进行向下取整得到 ⌊x⌋,那么我们取余20和这个概率是相差不大的。
%20+1也可以,向上取整
但是%21期望就是错的,那只是一个点,你认为是一段

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   srand(time(0));
   int n=10000,num=0;
   for(int i=0; i<n; i++)
   {
       double x=rand()%20,y=rand()%20;
       printf("%.2f %.2f\n",x,y);
   }
   return 0;
}

当然可以直接除以RAND_MAX,这样你就得到了在[0,1]的均匀分布

#include <bits/stdc++.h>
using namespace std;
int main()
{
   srand(time(0));
   for (int i = 0; i < 10000; i++)
   {
       double x = rand() * 1.0 / RAND_MAX * 20;
       double y = rand() * 1.0 / RAND_MAX * 20;
       cout << x << " " << y << endl;
   }
}

当然也有hack我spj的,确实可以直接构造

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
int main()
{
   srand(time(NULL));
   int f = 0;
   //cout<<7863*1.0/10000*400/10/10<<endl;
   for (int i = 1; i <= 7863; i++)
       printf("10 10\n");
   for (int i = 7864; i <= 10000; i++)
       printf("20 20\n");
}

10.6205: Alice与组合数学

这个就是隔板法,允许为空相当于还有k个盒子。那就变成了m+k个元素间的(m+k-1)个空中插入k个板,这个也就是选k个板即C(m+k-1,k)
C(n,m)=A(n,m)/m!=n!/(m!*(n-m)!)
除法可以转换为逆元,exgcd可以求逆元,这个题目模为素数p,也可以直接费马小定理用快速幂求(当模p不是素数的时候需要用到欧拉定理)
这个数比较大,但是我们可以打表
打表代码

#include <bits/stdc++.h>
using namespace std;
const int MD=2000000011;
int main()
{
   freopen("biao.out","w",stdout);
   int ans=1;
   int n=2e9;
   cout<<"1,";
   for(int i=1;i<n;i++)
   {
       ans=ans*1LL*i%MD;
       if(i%10000000==0)
       {
           cout<<ans<<",";
           if(i/10000000%8==7)
           {
               cout<<"\n";
           }
       }
       
   }
}

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MD = 2000000011;
int a[202] = {1,363886615,1817329888,1401794564,1568515239,1340732255,1841365283,114423256,
1710013049,873111513,422470864,864841454,949541789,914199643,1504834244,451416412,
476982384,995380174,285738271,1047564421,1730291751,355963849,679888167,1003626414,
1968026369,92868707,760038547,1341104556,517502883,1369387807,154431364,1725864818,
396419511,509976174,98541368,900233077,1153555724,1185340564,930357390,1471573826,
1496435175,886244661,637296345,1726165182,1730093463,578490442,429110182,765132821,
320117005,897944102,743002378,529658778,957369531,1845005758,1397870096,1585396968,
234589924,885803942,1027280954,6875812,33345696,1670035332,1389600544,1926213560,
103938049,162569644,1335168605,817594857,1828700932,1865810284,903071326,151733460,
563082174,572353793,1047303909,1916054910,144050002,1694197549,1651633766,867896551,
194680075,349487732,1162635753,1408150319,851803404,1027960630,1940461284,1807815531,
161122063,1905248264,151211130,1041016634,4574566,1181766896,426106079,111085282,
585472563,1523389384,1369130124,317922887,893121698,546648240,77432352,1314528338,
80816038,95848764,624955560,1075286855,1297388749,837446105,865060332,1820515234,
1764458302,334341956,430709653,1253108275,672147459,1339849324,915624807,312808217,
467424039,425822530,371827054,673135671,518217939,998379952,853494571,1705229059,
1753986288,431796094,1496193939,1213881512,1804886434,647159866,1410011118,1230521887,
1355590187,1639129510,1724882770,976128747,1243074205,1023652555,43770338,844534966,
129891004,295528401,1540452720,568012775,797157599,395662304,1046612799,1125498443,
465103566,1969411521,798751021,616030583,705400779,91211033,408211829,1077021044,
331886132,399310432,1208330079,1192509294,1432261076,152576524,1403549107,948130780,
1533741300,1094908497,1525188947,1532454860,1315061483,793767105,1527891232,722715838,
211291977,410188335,175751992,103276408,1724732690,362706835,1427301855,1289762254,
1617000280,1176076076,1422649453,1167385447,1395819263,678536044,1520852625,234262037,
343401380,1144418350,655052968,433195065,1880840640,708326218,874409850,1840153589
};
int INV(int x)
{
   return x == 1 ? x : 1LL * (MD - MD / x) * INV(MD % x) % MD;
}
int fac(int n)
{
   int now = n / 10000000;
   int ans = a[now];
   for (int i = now * 10000000 + 1; i <= n; i++)
       ans = ans *1LL* i % MD;
   return ans;
}
int C(int n,int m)
{
   return 1LL*fac(n)*INV(fac(m))%MD*INV(fac(n-m))%MD;
}
int main()
{
   int m,k;
   while (cin>>m>>k)
   {
       cout<<C(m+k-1,k)<<"\n";
   }
}

11.6206: Alice与中位数

所有的数求和为s,中位数是比s/2大,且最接近的。为什么是这样的呢,可以这样想,每个值t都有一个对应的集合值为s-t(t非0,没有空集,所以在s/2右边)
所以我们可以用bitset进行背包

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
//开辟s/2的空间
bitset<N> V;
int main()
{
   int n;
   scanf("%d", &n);
   int s = 0;
   //0可达
   V[0] = 1;
   for (int i = 0, x; i < n; i++)
       scanf("%d", &x), s += x, V |= V << x;
   //为了节省空间,我计算s/2左边的,然后s-i
   for (int i = s / 2;; i--)
       if (V[i])
       {
           printf("%d\n", s - i);
           break;
       }
}




为解题报告打分
暂时不评分

★★
★★★
★★★★
★★★★★
发表您的评论(若贴AC代码或发表禁止言论等违禁行为将被删除并扣除积分)

|返回 |   | 转到页头|
Copyright @ 2008-2024(浙ICP备2022001332号), TZOJ. All Rights Reserved.