上海市计算机学会竞赛平台2023年7月月赛丙组题目解题报告
上海市计算机学会竞赛平台2023年7月月赛丙组题目解题报告
T1 先行后列(孙誉文)
题意
| 第一列 | 第二列 | 第 m 列 | |||
|---|---|---|---|---|---|
| 第一行 | 1 | 2 | … | … | m |
| 第二行 | m+1 | m+2 | … | … | 2m |
| 第三行 | 2m+1 | 2m+2 | … | … | 3m |
| … | … | … | … | … | … |
| … | … | … | … | … | … |
| 第 n 行 | … | … | … | … | nm |
给我们2个数表示表格的m和n,再给我们1个数(我们设为int变量c),让我们输出这个数所在的行与列,输出先行后列。
分析
1)观察这个表格我们会发现表格含有规律,c%m的结果为c所在的数列,但是我们注意有1个特判:当c%m结果为0时,c所在数列便是第m列。上述逻辑可以用1个if语句来实现。并将所在列赋值给变量b.
2)在得到所在列b的情况下,我们可以借此来推算出c所在的行。随后便可以发现c/m+1的结果就是c所在的行,但是这里也要有1个特判:当c在m列时c/m的结果就已经是c所在的行,这里无需加1。同1这里也可以用if语句来完成。if语句中把所在行赋值给变量a。
3)以上逻辑会发现题目给的n毫无用处,所以可以不定义n变量。只要把读入写为cin>>m>>m>>c;2次读入m,最后的读入会把之前的读入覆盖。(不过保留n也不影响程序)
代码
#include <bits/stdc++.h>
using namespace std;
int m, c, a, b;
int main(){
cin>>m>>m>>c;
if (c%m==0) b=m;
else b=c%m;
if (b==m) a=c/m;
else a=c/m+1;
cout<<a<<" "<<b<<endl;
}
T2 兔子序列(陈致臻)
题目描述
序列 fi 的定义如下:
- f1=1
- f2=a
- 当 i>2 时,fi=fi−1+fi−2
给定一个 k,请问找到 j*,j 满足
fj≤k<fj+1
输入格式
- 第一行:单个整数 a
- 第二行:单个整数 k
输出格式
- 单个整数 j
数据范围
- 1≤a≤20
- 1≤k≤1,000,000,000
样例数据
输入: 1
10
输出: 6
说明: 10 介于 第6个数 与 第7个数 之间
分析
本题为斐波那契数列,不过是把第二个数由1换成了a而已,函数内加不加vh数组都行,这里加了以保证不超时
代码1
#include<bits/stdc++.h>
using namespace std;
int s;
long long a,k,ans,vh[100010];
int f(int s){
if(vh[s]!=-1) return vh[s];
if(s==1) return vh[s]=1;
if(s==2) return vh[s]=a;
return vh[s]=f(s-1)+f(s-2);
}
int main(){
memset(vh,-1,sizeof(vh));
cin>>a>>k;
for(int i=1;i<=100000;i++){
if(f(i)<=k&&k<f(i+1)){
ans=i;
break;
}
}
cout<<ans<<endl;
return 0;
}
代码2
#include<bits/stdc++.h>
using namespace std;
int s;
long long a,k,ans,vh[100010];
int f(int s){
if(s==1) return 1;
if(s==2) return a;
return f(s-1)+f(s-2);
}
int main(){
memset(vh,-1,sizeof(vh));
cin>>a>>k;
for(int i=1;i<=100000;i++){
if(f(i)<=k&&k<f(i+1)){
ans=i;
break;
}
}
cout<<ans<<endl;
return 0;
}
T3 数轴旅行(一)(顾浩骞)
题目描述
在数轴上,一共有
n
n
n 个景点,坐标分别为
x
1
,
x
2
,
x
3
,
.
.
.
.
,
x
n
x_1 ,x_2,x_3 ,....,x_n
x1,x2,x3,....,xn
你初始在
x
=
0
x=0
x=0位置,每次你可以往左
d
d
d 个单位或往右
d
d
d 个单位,请问为了访问到每一个景点,
d
d
d 最大可以取到多少?
输入格式
输入共两行:
第一行,第一个正整数
n
n
n
第二行,
n
n
n 个整数
x
1
,
x
2
,
x
3
,
.
.
.
.
,
x
n
x_1 ,x_2 ,x_3 ,....,x_n
x1,x2,x3,....,xn
输出格式
输出一行,表示答案
数据范围
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
10
1≤n≤10
1≤n≤10
对于
60
%
60\%
60% 的数据,
1
≤
n
≤
1
0
3
1≤n≤10^3
1≤n≤103
对于 100 % 100\% 100%的数据, 1 ≤ n ≤ 1 0 5 , − 1 0 9 ≤ x 1 , x 2 . . . x n ≤ 1 0 9 1≤n≤10^5,-10^9≤x_1,x_2...x_n≤10^9 1≤n≤105,−109≤x1,x2...xn≤109
样例数据
输入:
2
-4 4
输出:
4
输入:
3
-2 4 10
输出:
2
分析:本题就相当于求 n n n个数的最大公约数,知道三个的最大公约数是gcd(gcd(x,y),z),那么以此类推,就类似于递推,但是要处理 n = 1 n=1 n=1的情况,可以给 n + 1 n+1 n+1,也就是 n = 2 n=2 n=2,赋一个值,为 n = 1 n=1 n=1的倍数,当 n > 1 n>1 n>1,这个 n + 1 n+1 n+1用不到,当 n = 1 n=1 n=1,用 a 1 , a 2 a_1,a_2 a1,a2求最小公倍数,自然为 a 1 a_1 a1
代码:100分
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100010],ans;
int gcd(int x,int y){return y?gcd(y,x%y):x;}//求最大公约数
signed main(){
cin>>n;
for(int i=1;i<=n;++i){cin>>a[i];a[i]=abs(a[i]);}
a[n+1]=a[1]*2;//刚讲的特判
ans=gcd(a[1],a[2]);
for(int i=3;i<=n;++i) ans=gcd(ans,a[i]);//n<3时,这个循环不会执行,但是n=2时上面已经求了最大公约数,但n=1时没第二个数与他求,所以才加了个特判
cout<< ans << endl;
return 0;
}
T4 模糊匹配(二)(杨晓赫)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
有两个仅包含大写英文字母的字符串 S,T,且字符串 T 是 S 的一个子串。
但由于字符串 S 字迹模糊不清,其某些位置上的字符没有办法进行辨认,这些模糊的位置,用 ? 代替,我们将这个字符串称为S 。
现给定字符串 S ,T,请你求出,满足条件的所有可能的原字符串 S 中,字典序最小的一个。
输入格式
输入共两行:
第一行,一个字符串表示 S
第二行,一个字符串表示 T
输出格式
输出共一行,一个字符串表示答案
数据范围
设 ∣S∣,∣T∣ 分别为字符串 S,T 的长度
对于 30%的数据1≤∣T∣≤∣S∣≤10
对于 60%的数据,1≤∣T∣≤∣S∣≤10^2
对于 100%的数据,1≤∣T∣≤∣S∣≤10^4
数据保证存在字符串 S 满足条件
为保证字符串字典序最小,所有问号应尽可能填’A’,所以若T对应的S上部分没有’?‘或问号对应的是’A’,则应优先填它。
若无满足条件的,为保证字典序小,应从后往前找,尽可能让’?‘在前,方便填’A’。
#include <bits/stdc++.h>
using namespace std;
string s,t;
int main(){
cin>>s>>t;
bool f=true,fl=true;
int l=s.size(),l1=t.size();
for(int i=l-1;i>=0;--i){
int z=i,m=l1-1;
while(s[z]==t[m]||(t[m]=='A'&&s[z]=='?')){
if(m==0){
fl=false;
break;
}
z--;
m--;
}
}
if(fl){
for(int i=l-1;i>=0;--i){
int k=i,y=l1-1;
while(s[k]==t[y]||s[k]=='?'){
if(y==0){
f=false;
for(int j=i;j>=i-l1+1;--j){
if(s[j]=='?'){
s[j]=t[l1-1-i+j];
}
}
break;
}
k--;
y--;
}
if(f==false)
break;
}
}
for(int i=0;i<=l-1;++i){
if(s[i]=='?'){
cout<<"A";
continue;
}
cout<<s[i];
}
return 0;
}
T5 排列排序(高国皓)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
如果一个整数序列 a 1 , a 2 , … , a n a_1,a_2 ,…,a_n a1,a2,…,an 的每个数字都在 1 到 n 之间,且没有两个数字相等,则称这个序列为全排列。例如1,3,2 以及 4,3,2,1 都是全排列。
我们将所有的全排列排序,定义全排列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an 与 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,…,bm 的排序先后关系如下:
如果 n < m n<m n<m,则 a 序列更靠前
如果 n > m n>m n>m,则 b 序列更靠前
如果 n = m n=m n=m,则以字典序规则比较 a 序列与 b 序列,字典序更小的序列更靠前。
根据上述定义,可以得到
第 1 个全排列是 1
第 2 个全排列是 1 2
第 3 个全排列是 2 1
第 4 个全排列是 1 2 3
给定 k k k,请输出第 k k k 个全排列。
输入格式
单个整数:表示 k k k
输出格式
单独一行:表示第 k k k 个全排列
数据范围
30% 的数据 1 ≤ k ≤ 1000 1≤k≤1000 1≤k≤1000
60% 的数据 1 ≤ k ≤ 1 , 000 , 000 1≤k≤1,000,000 1≤k≤1,000,000
100% 的数据 1 ≤ k ≤ 1 0 15 1≤k≤10^{15} 1≤k≤1015
样例数据
输入:
5
输出:
1 3 2
说明:
根据上述定义可知全排列
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 中的填数方法有
A
n
n
A^n_n
Ann(
n
!
n!
n!) 种,更通俗地说,长度为
n
n
n 的全排列的个数有
n
!
n!
n! 个。
那么已知
k
k
k ,那么不断用
k
k
k 减去
1
!
,
2
!
,
3
!
,
.
.
.
,
c
n
t
!
1!,2!,3!,...,cnt!
1!,2!,3!,...,cnt!直至结果在保证为正的情况下无法再减,那么
c
n
t
cnt
cnt 就是第
k
k
k 个全排列的位数,假设下列代码所用变量已经定义,最终可以求出第
k
k
k 个全排列是位数为
p
o
s
pos
pos 的全排列中的第
k
k
k (两个
k
k
k 不一样)个,其中
i
i
i 存放的是位数(即当前阶乘的参数),
s
s
s 存放阶乘之和,
l
a
s
t
last
last 存放上一个的阶乘(即
(
i
−
1
)
!
(i-1)!
(i−1)! )。
int i=0;
while(++i){
s+=last*i;
if(s>k){
k=k-s+last*i;
pos=i;
s=last*i;
break;
}
last*=i;
}
当我们知道它是位数为
p
o
s
pos
pos 的全排列中的第
k
k
k 个时,而
s
s
s 表示
p
o
s
!
pos!
pos!,我们可以确定第一位,因为第一位有
p
o
s
pos
pos 种可能,而总计有
p
o
s
!
pos!
pos! 种可能,所以每一种
p
o
s
pos
pos 的可能性都对应着
(
p
o
s
−
1
)
!
(pos-1)!
(pos−1)! 种可能,而它是第
k
k
k 种,所以第一位是第
k
/
(
s
/
p
o
s
)
k/(s/pos)
k/(s/pos) (向上取整)个数字。假设第一位是第
c
c
c 个数字,去掉前面的
c
∗
(
s
/
p
o
s
)
c*(s/pos)
c∗(s/pos) 种可能,剩下的
k
−
(
c
−
1
)
∗
(
s
/
p
o
s
)
k-(c-1)*(s/pos)
k−(c−1)∗(s/pos) 就是它在长度为
p
o
s
−
1
pos-1
pos−1 的全排列中的位置。
然后设计递归。注意我是第…个数字,而不是…数字。因为当有的数字已经被使用后,不能再次使用,必须继续向后找。
void f(int pos,int s,int k)
p
o
s
pos
pos 表示位数,当其等于1时不再递归;
s
s
s 表示
p
o
s
!
pos!
pos! 用于计算可能性数量,注意实时的更新;
k
k
k表示的是位次,用于每次输出剩余的首位(即第
总位数
−
p
o
s
+
1
总位数-pos+1
总位数−pos+1 位)。
至于计算第
x
x
x 个数字,应当使用
v
h
vh
vh 数组进行标记,如果使用过了,即继续查找。
int i=0,cnt=(k+s/pos-1)/(s/pos);
while(cnt--)
if(vh[++i])++cnt;
cout<<i<<" ";
最后说明:因为没有仔细估计题目数据,数组就开到了10000000,具体大家可以算一算 1 0 15 10^{15} 1015 是 1 ! + 2 ! + 3 ! + . . . + n ! 1!+2!+3!+...+n! 1!+2!+3!+...+n!中的 n n n 值,最终算法复杂度为 O ( n 2 ) O(n^2) O(n2)(最坏情况)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int k,s,last=1,pos,i;
bool vh[10000000];
void f(int pos,int s,int k){
int i=0,cnt=(k+s/pos-1)/(s/pos);
int c=cnt;
while(cnt--)
if(vh[++i])++cnt;
cout<<i<<" ";
vh[i]=1;
if(pos>1)f(pos-1,s/pos,/*k-(c-1)*(s/pos)*/k%(s/pos)?k%(s/pos):s/pos);//注释中是第二种参数写法
}
signed main(){
cin>>k;
while(++i){
s+=last*i;
if(s>k){
k=k-s+last*i;
pos=i;
s=last*i;
break;
}
last*=i;
}
f(pos,s,k);
return 0;
}

浙公网安备 33010602011771号