校内天梯选拔赛决赛题解 --by LQJ
前言
整体评价
对于本场比赛除了B题存在题目没有解释清楚和A题防止AK的情况外其他的题目难度分布如下
签到题:I、L、M
简单题:C、D、K
中等题:E、F、J
难题:G、H
为了方便理解,本次题解采用C++语言(跟C的差距不是很大),并且不对A和B题进行讲解
C.卷王的时间安排
题意:找到第一个不满足条件的知识点,否则输出0就行
方法一:二分 + 差分 + 前缀和
先设置左右边界l和r,分别代表知识点的左端和右端,每次寻找mid = (l + r) / 2,每次寻找后通过差分和前缀和求是否满足条件,满足就更新l,反之更新r。直到找到对应的位置。
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll N = 1e5;
ll op[N];
struct ii{
ll a;
ll b;
ll c;
}p[N];
ll n,m;
ll dp[N];
bool check(ll mid){
std::memset(dp,0,sizeof dp);
for(ll i = 1; i <= mid; i ++){
dp[p[i].b] -= p[i].a;
dp[p[i].c + 1] += p[i].a;
}
for(ll i = 1; i <= n; i ++){
dp[i] += dp[i - 1];
}
for(ll i = 1; i <= n; i ++){
dp[i] += op[i];
if(dp[i] < 0) return true;
}
return false;
}
void solve(){
std::cin >> n >> m;
for(ll i = 1; i <= n; i ++) std::cin >> op[i];
for(ll i = 1; i <= m; i ++){
ll a,b,c;
std::cin >> a >> b >> c;
p[i] = {a,b,c};
}
ll l = 0,r = m + 1;
while(l + 1 < r){
ll mid = (l + r) / 2;
if(!check(mid)) l = mid;
else r = mid;
}
if(l > r) std::swap(l,r);
if(check(l)){
std::cout << -1 << "\n";
std::cout << l << "\n";
}
else if(check(r)){
std::cout << -1 << "\n";
std::cout << r << "\n";
}
else std::cout << 0 << "\n";
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
方法二:暴力搜索
由于本题数据范围很小,不用二分通过两层for循环也能够解决(出题人还是太善良了,没有卡数据)。
#include <iostream>
#define ll long long
const ll N = 1e5;
ll op[N];
void solve(){
ll n,m;
std::cin >> n >> m;
for(ll i = 1; i <= n; i ++) std::cin >> op[i];
bool ok = false;
ll res = -1;
for(ll i = 1; i <= m; i ++){
ll a,b,c;
std::cin >> a >> b >> c;
if(ok == true) continue;
for(ll j = b; j <= c; j ++){
op[j] -= a;
if(op[j] < 0){
ok = true;
res = i;
}
}
}
if(res == -1) std::cout << 0 << "\n";
else{
std::cout << -1 << "\n" << res << "\n";
}
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
D.最短的字符串
题意:通过不停的删除两个连续且不相同的字符,使得字符串最短(可以为0)
方法:此题为思维题,可以知道只要字符串当中有不同的字符,那么必然能够通过删除操作来减小字符串,所以剩下来的字符串里面只能存在一种类型的字符,或者没有字符。所以我们可以通过统计出现次数最多的字符个数x,与n / 2进行对比即可,如果小于等于n / 2那么最后肯定没有字符,即为0。如果大于n / 2那么最后结果为x - min(n / 2,n - x)。(推导的方法也许有多个,不唯一)
#include <iostream>
#include <string>
#include <cstring>
#include <map>
#define ll long long
void solve(){
ll n;
std::string a;
std::cin >> n >> a;
std::map<char,ll> op;
ll res = 0;
for(ll i = 0; i < n; i ++){
op[a[i]] ++;
res = std::max(res,op[a[i]]);
}
ll kk = n / 2;
if(res <= kk) std::cout << 0 << "\n";
else std::cout << res - std::min(kk,n - res) << "\n";
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
E.原来你也玩原神?
题意:给你一个包含空格的字符串,通过单一字符的改变,求最终的字符串。
方法:
第一点要注意是包含空格的,c++是用getline,而C可以用scanf(“%[^\n]”, str);
第二点要注意字符类型除了空格外,都是小写的英文字母,所以可以通过数组的形式来统计每个字母当前的类型
第三点要注意数字和字符串(字符)之间会有换行的(要吸收换行)
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll N = 100;
ll op[N];
void solve(){
std::string a;
ll n;
scanf("%lld\n",&n);
std::getline(std::cin,a);
ll m;
scanf("%lld\n",&m);
for(ll i = 1; i <= 26; i ++) op[i] = i;
for(ll i = 1; i <= m; i ++){
char a,b;
std::cin >> a >> b;
for(ll j = 1; j <= 26; j ++){
if(op[j] == (a - 'a' + 1)){
op[j] = (b - 'a' + 1);
}
}
}
for(ll i = 0; i < n; i ++){
if(a[i] == ' ') std::cout << a[i];
else{
std::cout << (char)(op[a[i] - 'a' + 1] - 1 + 'a');
}
}
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
F.小Q的回文质数
题意:如题目所示,给定一个范围要求你输出所有不仅满足回文性质还得要是质数的数才行
方法:直接暴力,首先对于偶数除了2都不是质数,其次对于每个不是偶数的进行判断,还要特判一下是否存在0个满足的,如果是就输出-1。
#include <iostream>
#define ll long long
const ll N = 100;
ll op[N];
bool check1(ll aa){
ll re = 0;
while(aa){
op[++re] = aa % 10;
aa /= 10;
}
for(ll i = 1,j = re; i <= j; i ++,j --){
if(op[i] == op[j]) continue;
else return false;
}
return true;
}
bool check2(ll aa){
if(aa == 1) return false;
for(ll i = 2; i * i <= aa; i ++){
if(aa % i == 0) return false;
}
return true;
}
void solve(){
ll n,m;
bool ok = false;
std::cin >> n >> m;
for(ll i = n; i <= m; i ++){
if(i == 2){
ok = true;
std::cout << i << "\n";
}
else{
if(i % 2 == 0) continue;
else{
if(check1(i) && check2(i)){
ok = true;
std::cout << i << "\n";
}
}
}
}
if(ok == false) std::cout << -1 << "\n";
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
G.小欣的连续段
题意:这里所说的连续段是只包含a或者b的一个区间称之为一段,如果一个区间即存在a又存在b那么就不能称之为一段,而是要继续分成若干段。对于t个段,其由n1 = t/2, n2 = (t+1)/2 两部分组成。根据隔板法,令a字母构建n1端,其组合数为 C(x - 1, n1 - 1),令b字母构建n2端,其组合数为 C(y - 1, n2 - 1)。综合为 C(x-1, n1-1) * C(y-1, n2-1),而a为n2段,b为n1段,依然成立。a,b的具体情况是互不影响的。
方法:隔板法(组合数 + 逆元)
组合数的公式:C(n,m) = n! / (m! * (n - m)!)
#include <iostream>
#define ll long long
const ll mod = 1e9 + 7;
ll op[10010];
ll qmi(ll a,ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll ny(ll aa){
return qmi(aa,mod - 2);
}
ll C(ll n,ll m){
if(m < 0 || m > n) return 0;
return op[n] * ny(op[m]) % mod * ny(op[n - m]) % mod;
}
void solve(){
op[0] = 1;
for(ll i = 1; i <= 2000; i ++){
op[i] = i * op[i - 1] % mod;
}
int x, y;
std::cin >> x >> y;
for(ll i = 1; i <= x + y; i ++){
ll aa = i / 2 + i % 2;
ll bb = i / 2;
std::cout << (C(x - 1, aa - 1) * C(y - 1, bb - 1) + C(y - 1, aa - 1) * C(x - 1, bb - 1)) % mod << "\n";
}
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
H."简单"的斐波那契数
题意:就是要求第x个的斐波那契数
方法:矩阵快速幂
这题暴力,循环,递归必然过不了,因为x的范围是1e18,数字非常庞大。所以要用到一个算法:矩阵快速幂。利用矩阵快速幂来求斐波那契数。也是相当于算法模板(出题人表示非常简单)
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll mod = 1e9 + 7;
struct ii{
ll a[5][5];
};
ii ss(ii aa,ii bb){
ii tt;
std::memset(tt.a,0,sizeof tt.a);
for(ll i = 0; i < 2; i ++){
for(ll j = 0; j < 2; j ++){
for(ll k = 0; k < 2; k ++){
tt.a[i][j] += (aa.a[i][k] % mod) * (bb.a[k][j] % mod);
}
}
}
return tt;
}
ll check(ll n){
ii aa,bb;
std::memset(aa.a,0,sizeof aa.a);
std::memset(bb.a,0,sizeof bb.a);
aa.a[0][0] = 1;
aa.a[0][1] = 1;
bb.a[0][0] = 1;
bb.a[1][0] = 1;
bb.a[0][1] = 1;
while(n){
if(n & 1) aa = ss(bb,aa);
bb = ss(bb,bb);
n >>= 1;
}
return aa.a[1][0];
}
void solve(){
ll n;
std::cin >> n;
std::cout << check(n) % mod << "\n";
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
I.LONGHUNPOMIE是游戏大王!
简单的思维题。题目意思就是是否存在一个区间使得敌人编号为k的最多,看似很难但是由数据范围可以知道区间范围可以选择1,这样的话就可以把这题简化成寻找是否存在一个数它是k,如果存在必然是YES(只选择这一个数),反之是NO
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
void solve(){
ll n,m;
std::cin >> n >> m;
bool ok = false;
for(ll i = 1; i <= n; i ++){
ll a;
std::cin >> a;
if(a == m) ok = true;
}
if(ok) std::cout << "YES\n";
else std::cout << "NO\n";
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
J.眼眸中的哀殇的矩阵翻转
题意:对于一个n * n的矩阵(n为偶数),可以不停的旋转90度。而要求无论怎么选转都要使得矩阵相应位置的字符一样,这也就说明了矩阵一个点对应的四个位置字符都要一样。比如说对于(i,j)它的对应点(j,n - i + 1),(n - i + 1,n - j + 1),(n - j + 1,i)字符都要相等。由这个性质可以在遍历二维数组时从1到n/2进行遍历,每次的点都找当前四个点,进行比较,找到最大的字符mm,之后返回mm与四个字符差值即可。
方法:思维 + 暴力枚举
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll N = 2e3;
char op[N][N];
ll n;
ll check(ll ii,ll jj){
char kk = 'a';
kk = std::max(kk,op[ii][jj]);
kk = std::max(kk,op[jj][n - ii + 1]);
kk = std::max(kk,op[n - ii + 1][n - jj + 1]);
kk = std::max(kk,op[n - jj + 1][ii]);
return (kk - op[ii][jj]) + (kk - op[jj][n - ii + 1]) + (kk - op[n - ii + 1][n - jj + 1]) + (kk - op[n - jj + 1][ii]);
}
void solve(){
std::cin >> n;
for(ll i = 1; i <= n; i ++){
std::string a;
std::cin >> a;
a = ' ' + a;
for(ll j = 1; j <= n; j ++){
op[i][j] = a[j];
}
}
ll res = 0;
for(ll i = 1; i <= n / 2; i ++){
for(ll j = 1; j <= n / 2; j ++){
res += check(i,j);
}
}
std::cout << res << "\n";
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
K.完美回文数
题意:此题要求的是满足不大于n的最大回文数a,并且存在x * x * x = a。也就是说a是某个整数的三次方。
方法一:预处理 + 暴力搜索
因为数据范围是1e18,所以能够知道x的范围最大不超过1e6,所以先预处理1到1e6的所有数,然后每次看它的a是多少,是否满足最大回文数性质,满足就先存进数组中,预处理完后,就对于每个数据,遍历一遍数组,满足条件的话求最大的数。
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll N = 1e5;
ll op[N],dp[100];
ll re = 0;
ll ke = 0;
bool check(ll jj){
ke = 0;
while(jj){
dp[++ke] = jj % 10;
jj /= 10;
}
for(ll i = 1, j = ke; i <= j; i ++,j --){
if(dp[i] == dp[j]) continue;
else return false;
}
return true;
}
void init(){
for(ll i = 1; i <= 1e6; i ++){
ll jj = (i * i * i);
if(check(jj)) op[++re] = jj;
}
return ;
}
void solve(){
ll n;
std::cin >> n;
ll res = 1;
for(ll i = 1; i <= re; i ++){
if(op[i] <= n) res = std::max(res,op[i]);
}
std::cout << res << "\n";
return ;
}
int main(){
ll t = 1;
init();
std::cin >> t;
while(t --)
solve();
return 0;
}
方法二:预处理 + 二分搜索
这题也可以二分搜索,当然这里也不做过多解释(题目根本没必要二分),直接上代码吧。
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
const ll N = 1e5;
ll op[N],dp[100];
ll re = 0;
ll ke = 0;
bool check(ll jj){
ke = 0;
while(jj){
dp[++ke] = jj % 10;
jj /= 10;
}
for(ll i = 1, j = ke; i <= j; i ++,j --){
if(dp[i] == dp[j]) continue;
else return false;
}
return true;
}
void init(){
for(ll i = 1; i <= 1e6; i ++){
ll jj = (i * i * i);
if(check(jj)) op[++re] = jj;
}
return ;
}
void solve(){
ll n;
std::cin >> n;
ll l = 0,r = re + 1;
while(l + 1 < r){
ll mid = (l + r) >> 1;
if(op[mid] <= n) l = mid;
else r = mid;
}
ll rk = 1;
if(op[l] <= n) rk = std::max(rk,op[l]);
if(op[r] <= n) rk = std::max(rk,op[r]);
std::cout << rk << "\n";
return ;
}
int main(){
ll t = 1;
init();
std::cin >> t;
while(t --)
solve();
return 0;
}
L.水题
签到题,经典比大小,不过多解释,直接上代码。
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
void solve(){
ll a,b,c;
std::cin >> a >> b >> c;
std::cout << std::max(a,std::max(b,c)) << "\n";
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}
M.水题又来了
也是签到题,也许有人会被四舍五入给迷惑,其实不用管,直接保留一位小数就行。
#include <iostream>
#include <string>
#include <cstring>
#define ll long long
void solve(){
double a,b,c;
std::cin >> a >> b >> c;
double res = std::max(a,std::max(b,c));
printf("%.1lf\n",res);
return ;
}
int main(){
ll t = 1;
// std::cin >> t;
while(t --)
solve();
return 0;
}