上海市计算机学会竞赛平台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;
}