上海计算机协会11月月赛-丙组解题报告
比赛结束后又测了一遍,与初测结果一致,得分确保无误。
T1-奇偶数的判定
奇偶数的判定
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定一个整数 n,若 n 是一个偶数,输出 even,若 n 是一个奇数,输出 odd。输入格式
单个整数:表示 n。输出格式
单个字符串:表示 n 的奇偶性数据范围
-1,000,000≤n≤1,000,000−1,000,000≤n≤1,000,000
样例数据
输入:
0
输出:
even
输入:
-1
输出:
odd
题目出处
看起来似乎很简单,但有个坑:如果n是负的奇数,那么n%2==-1!!!而非n%2==1!!!
代码如下(实测100分):
#include <bits/stdc++.h>
using namespace std;
int n;
int main(){
cin>>n;
if(n%2==0)cout<<"even"<<endl;//判定是否偶数,避免-1的问题
else cout<<"odd"<<endl;
return 0;
}
T2-搭积木
搭积木
内存限制: 256 Mb时间限制: 1000 ms
题目描述
小爱同学想要用积木搭起一个金字塔。为了结构稳定,金字塔的每一层要比上一层多一块积木。即搭建规则如下:金字塔的第 1 层需要放 1 块积木
金字塔的第 2 层需要放 2 块积木
金字塔的第 3 层需要放 3 块积木
…
金字塔的第 i 层需要放 i 块积木
现在小爱拿到了 n 块积木,请问他最高可以搭出多少层的金字塔?
输入格式
输入一个正整数 n,表示小爱手中的积木数量输出格式
输出一个正整数,表示小爱最高能搭的金字塔层数数据范围
对于 50% 的数据,1 ≤ n ≤ 1,0001≤n≤1,000
对于 100% 的数据,1 ≤ n ≤ 1,000,000,0001≤n≤1,000,000,000
样例数据
输入:
12
输出:
4
说明:
4层金字塔需要1+2+3+4=10块积木,而5层金字塔需要1+2+3+4+5=15块积木,所以小爱在有12块积木的情况下,最多搭4层金字塔
题目出处
其实是考三角形数,搭建第i个金字塔需要i*(i+1)/2个积木。
就有了方法1:(100分)
#include <bits/stdc++.h>
using namespace std;
int n;
int main(){
cin>>n;
for(int i=1;i<=100000;++i)
if(n<i*(i+1)/2){
cout<<i-1<<endl;
return 0;
}
return 0;
}
方法2:这题用while实现合适(100分)
#include <bits/stdc++.h>
using namespace std;
int n,i;
int main(){
cin>>n;
while(n>i*(i+1)/2)++i;
cout<<i-1<<endl;
return 0;
}
方法3(实测100分):
#include <bits/stdc++.h>
using namespace std;
int n;
int main(){
cin>>n;
for(int i=1;i<=100000;++i){
if(n<i){
cout<<i-1<<endl;
return 0;
}
n-=i;
}
return 0;
}
T3-最长平台
最长平台
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定一个整数数列 a_1,a_2, ,…,a_n,请找出最长平台,并输出最长平台的数量(数字相等但位置不同的平台算作不同的平台)。所谓平台,就是指数列中一段连续的、完全相等的数字,单个数字可以成为一个平台。
输入格式
第一行:单个整数 n
第二行:n 个整数 a_1,a_2,…,a_n输出格式
两个整数:表示最长平台的长度与最长平台的数量数据范围
对于 50% 的数据,n≤1000
对于 100% 的数据,n≤500,000
1≤a≤1,000,000
样例数据
输入:
7
2 2 2 1 3 3 3
输出:
3 2
说明:
最长平台为2 2 2或3 3 3
输入:
5
3 1 4 1 5
输出:
1 5
说明:
每个数字单独成一个平台
题目出处
由于数据量并不很大,可以枚举所有数,并统计平台数量(100分):
#include <bits/stdc++.h>
using namespace std;
int n,a[500010],ans,s=1,b[1000010];
int main(){
cin>>n;
cin>>a[1];
for(int i=2;i<=n;++i){//枚举所有数
cin>>a[i];
if(a[i]==a[i-1]){//判断是否一致(即是否是平台)
++s;//是的话长度增加
}
else{
if(s>ans)ans=s;//更新最长值
b[s]++;//统计
s=1;
}
}
if(s>ans)ans=s;
b[s]++;
s=1;//统计最后一个
cout<<ans<<" "<<b[ans]<<endl;
return 0;
}
T4-积木染色
积木染色
内存限制: 256 Mb时间限制: 1000 ms
题目描述
有 n 块积木排成一排,小爱需要给每块积木染色,颜色有 m 种,请问有多少种方法,能使相邻两块积木的颜色均不相同?输入格式
输入两个正整数n,m输出格式
输出满足条件的方案数模10^9+7的结果数据范围
对于 30% 的数据,1≤n,m≤10
对于 60% 的数据,1≤n,m≤1e4对于 100% 的数据,1≤n≤1e15 ,1≤m≤1e9
样例数据
输入:
3 2
输出:
2
说明:
合法的染色方案有:{1,2,1} {2,1,2}
题目出处
这是一个排列组合,第1个位置有m个填法,第2个位置有(m-1)个填法,第3个位置有(m-1)个填法,…,,第n个位置有(m-1)个填法。
方法1:for循环实现
初学者大都会想到这个方法,易于实现,初测60分。
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long n,ans,m;
int main(){
cin>>n>>m;
ans=m;//先乘m
for(int i=2;i<=n;++i){
ans*=m-1;//循环乘(m-1)
ans%=mod;//利用余数定理除以(1e9+7)
}
cout<<ans<<endl;
return 0;
}
方法2:幂取模
CSP-J-T1有这样例子。(递归实现)
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long n,m;//数据规模是long long,要注意
long long f(long long step,long long sq){
if(step==1)return sq;
int nod;
if(step%2==0)nod=f(step/2,sq)*f(step/2,sq)%mod;
else nod=f(step/2,sq)*f(step/2,sq)%mod*sq%mod;//递归
return nod;
}
int main(){
cin>>n>>m;
cout<<f(n-1,m-1)*m%mod<<endl;//注意要再模一次,血的教训令我明白了一切(否则30分)
return 0;
}
仍然60分!!!超时了!!!
递归超时了,但内存没爆,说明层数不深,可行。
递归超时怎么办?
答案是记忆化。
方法3:幂取模+记忆化(初测100分)
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long n,m,a[200000000]/*记忆化数组*/,b;
long long f(long long step,long long sq){
if(step==1)return sq;
int nod;
if(step<200000000&&a[step]!=0/*短路表达式,避免下标越界*/)nod=a[step];
else {
if(step%2==0)nod=f(step/2,sq)*f(step/2,sq)%mod;
else nod=f(step/2,sq)*f(step/2,sq)%mod*sq%mod;
if(step<200000000)a[step]=nod;
}
return nod;
}
int main(){
cin>>n>>m;
cout<<f(n-1,m-1)*m%mod<<endl;
return 0;
}
方法4:优化方法2(100分)
细心的读者会看到
if(step%2==0)nod=f(step/2,sq)*f(step/2,sq)%mod;
else nod=f(step/2,sq)*f(step/2,sq)%mod*sq%mod;
中递归f被执行了两遍,也就是说复杂度被无缘无故平方了。
可以改为
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
long long n,m,b;
long long f(long long step,long long sq){
if(step==0)return 1;
long long nod=f(step/2,sq);
if(step%2==0)nod=nod*nod%mod;
else nod=nod*nod%mod*sq%mod;
return nod;
}
int main(){
cin>>n>>m;
cout<<f(n-1,m-1)*m%mod<<endl;
return 0;
}
T5-出栈序列
出栈序列
内存限制: 256 Mb 时间限制: 1000 ms
题目描述
给定一个长度为n的、仅由小写字母组成的字符串,将其按序依次放入栈中。请问在所有可能的出栈序列中,字典序最小的出栈序列是多少?
输入格式
输入第一行, 一个正整数n
输入第二行,一个长度为n的字符串输出格式
输出所有出栈序列中,字典序最小的出栈序列数据范围
对于30%的数据,1≤n≤10
对于60%的数据,1≤n≤1e3
对于100%的数据,1≤n≤1e5样例数据
输入:
3
yes
输出:
esy
说明:
字符y、e、s依次进栈,所有出栈的可能性有:
{yes}、{yse}、{eys}、{esy}、{sey}
其中 {esy} 的字典序最小
题目出处
看起来很难……
第一个方法是穷举(用dfs),20-30分(就不写了),加上剪枝大概60分。
其次是要仔细分析。发现:
if(栈空&&有待入栈字符||有待入栈字符&&栈顶元素字典序<待入栈最小字典序字符){
入栈;
}else{
出栈;
}
所以有了这样的程序(100分):
#include <bits/stdc++.h>
using namespace std;
string st;
int pos,pop;//pop为待入栈字符指针
char a[100010];
int main(){
cin>>pos>>st;
a[pos-1]=st[pos-1];
for(int i=pos-2;i>=0;--i)a[i]=min(st[i],a[i+1]);//用O(n)复杂度算后缀最小值
char ans[1000010];//数组模拟栈
int top=0;//栈顶指针
while(top!=0||pop<pos){
if(top==0/*循环仍继续,说明pop>=pos,所以无须追加另一条件*/||pop<pos&&a[pop]<ans[top]){
top++;
ans[top]=st[pop++];//入栈
}
else{
cout<<ans[top];
top--;//出栈
}
}
return 0;
}