上海市计算机学会2023年2月月赛
T1 格式改写
题目描述
给定一个仅由拉丁字符组成字符序列,需要改写一些字符的大小写,使得序列全部变成大写或全部变成小写,请统计最少修改多少个字符才能完成这项任务。
输入格式
一个字符序列:保证仅由拉丁字符构成
输出格式
单个整数:表示最少修改次数
数据范围
设输入的字符数量为 n,则保证1≤n≤100,000
样例数据
输入:
TheQuickBrownFoxJumpsOverTheLazyDog
输出:
9
说明:
将大写改小写
因为仅由拉丁字符组成,最简单的思路就是枚举大小写字符数量然后进行比较输出。
#include <bits/stdc++.h>
using namespace std;
string s;
int main(){
cin>>s;
int n=s.size(),CNT=0;
for(int i=0;i<n;++i)
if(s[i]>='A'&&s[i]<='Z')++CNT;
int cnt=n-CNT;
if(cnt>CNT)cout<<CNT<<endl;//如果小写字符多,则将所有大写改成小写
else cout<<cnt<<endl;//否则将小写改为大写
return 0;
}
T2 倍数统计
题目描述
给定整数 a,b 与正整数 c,求出在 a 到 b 之间(包含 a 与 b)有多少整数是 c 的倍数。
输入格式
第一行:两个整数 a 与 b;
第二行:单个正整数 c。
输出格式
单个整数:表示答案。
数据范围
−109≤a≤b≤109
1≤c≤109
样例数据
输入:
4 6
5
输出:
1
这题直接枚举肯定是不可以的了。一开始思路是(设较小数为a,较大数为b)从a向后枚举,一直找到c的倍数,b向前枚举一直找到c的倍数。最后除一下(虽然初测过了,但是仍会有超时风险,如:-999999999 999999999 1000000000)
#include <bits/stdc++.h>
using namespace std;
int a,b,c;
int main(){
cin>>a>>b>>c;
if(a>b)swap(a,b);
while(a%c!=0)++a;
while(b%c!=0)--b;
cout<<(b-a)/c+1<<endl;
return 0;
}
当然有更简单的方法,先考虑(b>a>0):
a到b之间c的倍数=1到b之间c的倍数数量-1到a-1之间c的倍数数量
不过明白这一点也就好办了,如果都是负数,可以把他们都当作正数来处理;如果一个正数一个负数,(设a<0,b>0)那么先算1到b,再算-1到a,也就是1到-a。
#include <bits/stdc++.h>
using namespace std;
int main(){
int a,b,c;
cin>>a>>b>>c;
if(a>0&&b>0)cout<<b/c-(a-1)/c<<endl;
else if(a<0&&b<0)cout<<(0-a)/c-(0-b-1)/c<<endl;
else cout<<(0-a)/c+1+b/c<<endl;
return 0;
}
T3 区间的并
区间的并
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定一个数轴上的 nn 个闭区间,第 ii 个闭区间的两端点为[a_i,b_i],它们的并集可以表示为若干不相交的闭区间,请按照左端点从小到大的顺序输出这些区间的并集。
输入格式
第一行:单个整数 n;
第二行到第 n+1 行:每行两个整数 a_i 与 b_i 表示一个闭区间 [a_i,b_i]。
输出格式
若干行:表示输入区间的并集。每行两个整数,表示一个闭区间的两个端点,这些闭区间应该按照起点从小到大排序。
数据范围
对于 50% 的数据,1≤n≤104,0≤a_i≤b_i≤104
对于 100% 的数据,1≤n≤105,0≤a_i≤b_i≤109
样例数据
输入:
3
10 12
1 3
2 5
输出:
1 5
10 12
这题可以简单标记,但是有一个问题:[1,2] [3,4]该不该合并?
有一个方法:下标*2标记
但是我懒先不写了
有一个类似的题目,这题……数据小,但是还是可以学习学习。
可以进行判断区间合并(是O(n2)算法),可以通过sort排序将比较优化到O(n)。
#include <bits/stdc++.h>
using namespace std;
int n,cnt;
struct nod{
int a,b;
}q[100010];
void work(){
int x=q[1].a,y=q[1].b;
for(int i=2;i<=n;++i){
if(q[i].a>y){
cout<<x<<" "<<y<<endl;
x=q[i].a,y=q[i].b;//就是一个单独闭区间
}
else{
y=max(y,q[i].b);//如果可以合并
}
}
cout<<x<<" "<<y<<endl;
}
bool cmp(nod q,nod p){
return q.a<p.a;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i)
cin>>q[i].a>>q[i].b;
sort(q+1,q+1+n,cmp);
work();
return 0;
}
T4 平分数字(一)
平分数字(一)
内存限制: 256 Mb时间限制: 1000 ms
题目描述
给定 n 个整数:a_1,a_2 ,⋯,a_n,请判定能否将它们分成两个部分(不得丢弃任何数字),每部分的数字之和一样大。
输入格式
第一行:单个整数 n;
第二行:n 个整数,表示 a_1,a_2,…,a_n。
输出格式
若能否平分,输出 Matched,否则输出 No
数据范围
对于 50% 的数据,1≤n≤18;
对于 100% 的数据,1≤n≤24;
−10,000,000≤a_i≤10,000,000
样例数据
输入:
4
1 2 3 4
输出:
Matched
说明:
1 + 4 = 2 + 3
输入:
3
2 2 2
输出:
No
这题上手,直接爆搜,时间复杂度O(2n)
最坏情况复杂度:16777216,不会超时,裸搜题目。但是,要注意:选择次序与结果无关!什么意思----先选择与后选择结果一样(否则复杂度就是O(nn))
#include <bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,a[25],vh[25];
bool f=1;//找到一种可行方案就把它标成false退掉搜索
void dfs(int step,int s1,int s2){
if(!f)return;
if(step==n){
if(s1==s2){
cout<<"Matched"<<endl;
f=0;
}
return;
}
for(int i=1;i<=25;++i){
if(vh[i])continue;
vh[i]=1;
dfs(step+1,s1+a[i],s2);
dfs(step+1,s1,s2+a[i]);
vh[i]=0;
break;
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
dfs(0,0,0);
if(f)cout<<"No"<<endl;
return 0;
}
T5 圆环三染色
圆环三染色
内存限制: 256 Mb时间限制: 1000 ms
题目描述
有一个圆环上有 n 个点,一个染色方案需要为每个点分配三种颜色中的一种,且圆环上相邻的点颜色不能相同。
请求出有多少种染色方案。答案可能很大,输出模 1,000,000,007 的余数。
输入格式
单个整数表示 n。
输出格式
表示方案数模 1,000,000,007 的余数。
数据范围
对于 30% 的数据,1≤n≤20;
对于 60% 的数据,1≤n≤1,000,000;
对于 100% 的数据,1≤n≤1018
样例数据
输入:
1
输出:
3
输入:
3
输出:
6
暴力就不写啦!!!
这题60%数据可以用递归递推解决。(需要以2,3为边界,以1为边界是错误的)
如果n>3:
先考虑第1,n-1,n个的颜色。
Firstly:1的颜色==第n-1的颜色,那么第n个可以有2种可能。那么1到n-1共有多少可能?----因为1的颜色=n-1的颜色,所以1的颜色!=n-2的颜色,所有共有f(n)种可能,再乘上第n个的可能,共有2*f(n-2)可能。
Secondly:1的颜色!=第n-1的颜色,那么第n个只有1种可能。1到n-1共有f(n-1)种可能,再乘上第n个的可能,共有f(n-1)的可能。
So,
f
(
n
)
=
2
∗
f
(
n
−
2
)
+
f
(
n
−
1
)
f(n) = 2*f(n-2)+f(n-1)
f(n)=2∗f(n−2)+f(n−1)
可以过掉6个点。
记忆化递归:
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int vh[1000010]={0,3,6,6};
int f(int n){
if(vh[n])return vh[n];
return vh[n]=(f(n-1)%mod+2*f(n-2)%mod)%mod;
}
signed main(){
int n;
cin>>n;
cout<<f(n)<<endl;
return 0;
}
递推(迭代):
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int vh[10]={0,3,6,6};
signed main(){
int n;
cin>>n;
int a=6,b=6;
for(int i=4;i<=n;++i){
int a1=b;
b=(a*2+b)%mod;
a=a1;
}
if(n>3)cout<<b<<endl;
else cout<<vh[n]<<endl;
return 0;
}
递推(数组记录):
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int vh[1000010]={0,3,6,6};
signed main(){
int n;
cin>>n;
for(int i=4;i<=n;++i){
vh[i]=(vh[i-1]+vh[i-2]*2)%mod;
}
cout<<vh[n]<<endl;
return 0;
}
一开始分析问题是1,n-1,n的可能是8,那总共就是2n经过找规律和一系列蒙猜,发现如果是奇数+2,偶数再-2
这儿呢,还要用一个快速幂。
#include <bits/stdc++.h>
#define mod 1000000007
#define int long long//必须开long long否则会爆。
using namespace std;
int n;
int f(int step){
if(step==1)return 2;
int t=f(step/2);
if(step%2==0)return t*t%mod;
return t*t%mod*2%mod;
}
signed main(){
cin>>n;
if(n==1)cout<<3<<endl;
else if(n==2)cout<<6<<endl;
else if(n==3)cout<<6<<endl;
else if(n%2==1)cout<<(f(n)-2+mod)%mod<<endl;//注意不要mod过再减,否则会有-1
else cout<<(f(n)+2)%mod<<endl;
return 0;
}