天元公学-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%10n && (i%k)%n0){
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=x10+ch-'0';
ch=getchar();
}
return xf;
}
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(op1)
{
ck=-1;
break;
}
op=1;
if(a[i]'*'||a[i]'/') ck=1;
}
}
if(op1||ck-1) puts(wq);
else if(ck1) 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 (is.size()-1&&s[i]<'0') f1=1;
}
if (f21) 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 (t1t1+t2t2==t3t3+t4t4){
if (t1t1+t2t2>maxdistance){
maxdistance=t1t1+t2t2;
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;

浙公网安备 33010602011771号