上海市计算机学会2023年1月月赛
上海市计算机学会2023年1月月赛
T1 实验日志
实验日志
内存限制: 256 Mb时间限制: 1000 ms
题目描述
小爱正在完成一个物理实验,为期n天,其中第ii天,小爱会记录a_i 条实验数据在实验日志中。
已知小爱的实验日志每一页最多纪录m条数据,每天做完实验后他都会将日志合上,第二天,他便从第一页开始依次翻页,直到找到第一个有空白位置的页码为止,开始新一天的数据记录。
请问在整个实验过程中,小爱每天为了找到第一个空白位置,需要翻多少页?
输入格式
输入共两行
第一行,两个正整数n,m。
第二行,n个正整数,表示每天的数据条数。
输出格式
输出共一行,n个正整数,分别表示每一天开始实验前,需要翻的页数。
数据范围
对于 30% 的数据,1≤n≤100
对于 60% 的数据,1≤n≤104
对于 100% 的数据,1≤n≤105
1≤m,a_i≤104
样例数据
输入:
4 10
7 8 5 12
输出:
0 0 1 2
说明:
第一天不用翻页
第二天开始前,由于只记了7条,仍是从第一页开始,不用翻页
第三天开始前,共记录了15条,则是从第二页开始,需翻1页
第四天开始前,共记录了20条,由于第二页已写满,则是从第三页开始,需翻2页
其实……没有那么玄妙,经过找规律发现:翻的页数=总记录数量整除每页记录的数量(可以证明:当前记录数量假设为a,每页可写数为b
共有a/b页被写满,应从第a/b+1页写起,应翻a/b页。)
#include <bits/stdc++.h>
using namespace std;
int n,m,a[100010],cnt;
int main(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
cout<<cnt/m<<" ";
cnt+=a[i];
}
return 0;
}
T2 凯撒加密
凯撒加密
内存限制: 256 Mb时间限制: 1000 ms
题目描述
恺撒密码是一种广为人知的加密技术。恺撒把需要加密的字母按字母表向后移动 3 位,替换成密文字母。例如,所有的 A 将被变成 D,B 变成 E 等等。若要加密最后三个字母,则需要折回到前三个字母,比如 x 变 a,y 变 b,z 变 c。
例如以下明文
TheQuickBrownFoxJumpsOverTheLazyDog
将被加密成
WkhTxlfnEurzqIraMxpsvRyhuWkhOdcbGrj
给定一段只由拉丁字母组成的字符序列,请将它用凯撒加密成密文。
输入格式
一个字符序列:表示需要加密的明文
输出格式
一个字符序列:表示加密后的密文
数据范围
设输入的字符数量为 n,则保证 1≤n≤100,000
样例数据
输入:
TheQuickBrownFoxJumpsOverTheLazyDog
输出:
WkhTxlfnEurzqIraMxpsvRyhuWkhOdcbGrj
这题一开始测试数据有误,不过现在改过了
这题逐字符读入,字符串(cin或getline均可)读入均可,只需要创建一个字符数组用于存字母表(当然由于凯撒加密的越界问题,第27,28,29个字符分别应该为a,b,c)。
逐字符读入处理
#include <bits/stdc++.h>
using namespace std;
char ch;
int main(){
while(cin>>ch){
if(ch>='a'&&ch<='z')ch=(ch-'a'+3)%26+'a';
else if(ch>='A'&&ch<='Z')ch=(ch-'A'+3)%26+'A';
cout<<ch;
}
cout<<endl;
return 0;
}
字符串读入处理
#include <bits/stdc++.h>
using namespace std;
string s;
int main(){
getline(cin,s);//也可以用cin>>s;
int pos=s.size();
for(int i=0;i<pos;++i){
char ch=s[i];
if(s[i]>='a'&&s[i]<='z')ch=(ch-'a'+3)%26+'a';
else if(s[i]>='A'&&s[i]<='Z')ch=(ch-'A'+3)%26+'A';
cout<<ch;
}
cout<<endl;
return 0;
}
T3 找零
找零
内存限制: 256 Mb时间限制: 1000 ms
题目描述
有一台自动售票机,每张票卖 5 元。售票机可以接受 5 元、10 元、20元的纸币。接受大面额纸币时,若没有足够的零钱,售票机将拒绝售票并将纸币退还给客户,若有零钱足够,售票机必须出票并且找零。
一开始,售票机里没有任何零钱。
每位客户只买一张票也只会塞一张纸币。按照购票顺序,给定售票机收到的 n 张纸币的面额。请统计售货机最多能够卖出多少张票。
输入格式
第一行:单个整数 n
第二行:n 个整数表示 n 名客户分别塞入的纸币面额,保证只能是 5、10、20 中的一个。
输出格式
单个整数:表示售票机卖出的最多票数。
数据范围
1≤n≤50,000
样例数据
输入:
8
10 5 5 5 10 10 20 20
输出:
6
说明:
由于没有零钱,无法把票卖给第一个人和最后一个人
逻辑还算简单,存在一个贪心----优先使用面额大的钞票(即10元的钞票),因为面额小的钞票灵活性大。(a5存应付5元钞票数量,以此类推,s5存目前lingshouji
#include <bits/stdc++.h>
using namespace std;
int n,ans,s5,s10,s20,a;
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a;
int aa=a;
a-=5;
int a20=a/20;
if(s20<a20)a20=s20;
a-=a20*20;
int a10=a/10;
if(a10>s10)a10=s10;
a-=a10*10;
int a5=a/5;
a-=a5*5;
if(a5<=s5){
s20-=a20;
s10-=a10;
s5-=a5;
++ans;
if(aa==5)s5++;
else if(aa==10)s10++;
else s20++;
}
}
cout<<ans<<endl;
return 0;
}
T4 新年灯会
新年灯会
内存限制: 256 Mb时间限制: 1000 ms
题目描述
新春佳节之际,路上挂起了一排喜气洋洋的大红灯笼,从左至右编号分别为1,2,…,n。但小爱发现,目前有p个灯笼不亮了,很是影响美观。
请你帮助小爱计算,最少修复多少个灯笼,便可使道路上有连续m个亮着的大红灯笼?
输入格式
输入共两行:
第一行,三个正整数分别表示n,m,p
第二行,p个正整数,表示已经不亮的灯笼编号
输出格式
输出共一行,一个正整数表示答案
数据范围
对于 30% 的数据,1≤m,p≤n≤100
对于 60% 的数据,1≤m,p≤n≤104
对于 100% 的数据,1≤m,p≤n≤105
样例数据
输入:
8 5 3
5 1 8
输出:
1
说明:
只需把5号灯笼修好即可
其实这题就是问一个长为m的序列坏了的灯笼数量,最简单的就是两层for循环,进行计算;优化一些可以使用前缀和;最后呢,可以得出(设a为左端点):
a到a+m-1的坏了的灯笼数量为a-1到a+m-2的坏了的数量再减去a-1再+(a+m-1),就可以维护出一个数量。
我的前缀和代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,p,a[100010],s[100010],ans=1e9;
int main(){
cin>>n>>m>>p;
int pi;
for(int i=1;i<=p;++i){
cin>>pi;
a[pi]=1;
}
for(int i=1;i<=n;++i){
if(a[i]==0)s[i]=s[i-1]+1;
else s[i]=s[i-1];
}
for(int i=1;i<=n-m+1;++i){
int pos=m-(s[i+m-1]-s[i-1]);
if(ans>pos)ans=pos;
}
cout<<ans<<endl;
return 0;
}
T5 积木染色(二)
积木染色(二)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
n 块积木排成一排,需要给每块积木染色,颜色有 m 种。请问有多少种方法,从第二块积木开始统计,恰有 p 块积木与前一块积木颜色不同?
输入格式
三个整数分别表示 n,m,p
输出格式
输出满足条件的方案数模109+7的结果
数据范围
对于 30% 的数据,1≤n,m≤10
对于 60% 的数据,1≤n,m≤500
对于 100% 的数据,1≤n,m≤5000,1≤p<n
样例数据
输入:
4 2 2
输出:
6
说明:
设两种颜色分别为1,2
则有:1121,1211,1221,2122,2112,2212共计6种
这题我一开始思路是搜索(为了方便记忆化,我把搜索设成了有返回值的)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,m,p,ans,vh[5010][5010];
int dfs(int pp,int step){
if(pp>p)return 0;
if(step==n){
if(pp==p)return m;
else return 0;
}
if(vh[pp][step])return vh[pp][step];
int ans=dfs(pp,step+1);
for(int i=1;i<m;++i)ans+=dfs(pp+1,step+1);
vh[pp][step]=ans%mod;
return ans%mod;
}
signed main(){
cin>>n>>m>>p;
cout<<dfs(0,1)%mod<<endl;
return 0;
}
但是……这个记忆化不规范。
规范的记忆化应该将初始值设成不可能的值。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,m,p,ans,vh[5010][5010];
int dfs(int pp,int step){
if(pp>p)return 0;
if(step==n){
if(pp==p)return m;
else return 0;
}
if(vh[pp][step]!=-1)return vh[pp][step];
int ans=dfs(pp,step+1);
for(int i=1;i<m;++i)ans=(ans+dfs(pp+1,step+1))%mod;
vh[pp][step]=ans;
return ans;
}
signed main(){
memset(vh,-1,sizeof(vh));
cin>>n>>m>>p;
cout<<dfs(0,1)<<endl;
return 0;
}
然后再将没必要的循环省一省,就是满分了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,m,p,ans,vh[5010][5010];
int dfs(int pp,int step){
if(vh[pp][step]!=-1)return vh[pp][step];
if(pp>p)return vh[pp][step]=0;
if(step==n){
if(pp==p)return vh[pp][step]=m;
else return vh[pp][step]=0;
}
int ans=(dfs(pp,step+1)+dfs(pp+1,step+1)*(m-1)%mod)%mod;
vh[pp][step]=ans;
return ans;
}
signed main(){
memset(vh,-1,sizeof(vh));
cin>>n>>m>>p;
cout<<dfs(0,1)<<endl;
return 0;
}
但是我们还要“知其所以然”。
每当面临一次选择,都有两种可能:
1.与前面颜色相同。
2.与前面颜色不同。
用递归式表达就是(n就是已选择数量,p是不同颜色数量)如果不同颜色数量此时还小于题目规定的p:
f
(
n
,
p
)
=
f
(
n
+
1
,
p
)
+
(
m
−
1
)
×
f
(
n
+
1
,
p
+
1
)
f(n,p) =f(n+1,p)+(m-1)×f(n+1,p+1)
f(n,p)=f(n+1,p)+(m−1)×f(n+1,p+1)
否则:
f
(
n
,
p
)
=
f
(
n
+
1
,
p
)
f(n,p) =f(n+1,p)
f(n,p)=f(n+1,p)