天元公学-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;

浙公网安备 33010602011771号