上海市计算机学会竞赛平台2023年6月月赛丙组题目解题报告
T1-差分求和
题目描述
给定一个整数序列,对其中任意两个数,计算后项减前项的差,输出这些差的差。
例如对于
a
1
,
a
2
,
a
3
,
a
4
a_1,a_2,a_3,a_4
a1,a2,a3,a4 来说,输出
(
a
2
−
a
1
)
−
(
a
3
−
a
1
)
−
(
a
3
−
a
2
)
−
(
a
4
−
a
1
)
−
(
a
4
−
a
2
)
−
(
a
4
−
a
3
)
(a_2-a_1)-(a_3-a_1)-(a_3-a_2)-(a_4-a_1)-(a_4-a_2)-(a_4-a_3)
(a2−a1)−(a3−a1)−(a3−a2)−(a4−a1)−(a4−a2)−(a4−a3)
输入格式
第一行:单个整数
n
n
n
第二行:
n
n
n 个整数
a
1
,
a
2
,
…
,
a
n
a_1,a_2,…,a_n
a1,a2,…,an
输出格式
单个整数表示结果
数据范围
对
50
%
50\%
50% 的数据,
1
≤
n
≤
500
,
1
≤
a
i
≤
500
1≤n≤500,1≤a_i≤500
1≤n≤500,1≤ai≤500
对
50
%
50\%
50% 的数据,
1
≤
n
≤
500
,
000
,
1
≤
a
i
≤
500
,
000
1≤n≤500,000,1≤a_i≤500,000
1≤n≤500,000,1≤ai≤500,000
样例数据
输入:
3
1 2 3
输出:
-2
分析1:暴推yyds!!!
对一些刚学过for循环的正好实用,但缺点就是两层嵌套,只有50分
代码1:50分
#include <bits/stdc++.h>
using namespace std;
long long n,a[500000];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
long long ans=a[2]-a[1];//因为第一项总是(a2-a1),所以先算了
for(int i=3;i<=n;++i){//从第三个开始,不要担心n小于三,因为小于三就不执行了
for(int j=1;j<i;++j){
ans-=a[i]-a[j];//相减
}
}
cout<<ans << endl;
return 0;
}
分析2:偷袭!!!
O ( n 2 ) O(n^2) O(n2)效率太低了,只能有五十分,那只能采取偷袭的方法,出其不意!!!
用1,2,3三个数距离
根据上面的公式是:
( 2 − 1 ) − ( 3 − 1 ) − ( 3 − 2 ) = − 2 (2-1)-(3-1)-(3-2)=-2 (2−1)−(3−1)−(3−2)=−2
把公式拆开后:
2 − 1 − 3 + 1 − 3 + 2 2-1-3+1-3+2 2−1−3+1−3+2
简单排一下后:
2 − 1 + 1 + 2 − 3 − 3 2-1+1+2-3-3 2−1+1+2−3−3
看到了吗,除了原来的 2 − 1 2-1 2−1,还要加一下从二开始每个点的前缀和,然后再减去从三开始的每个数去乘当前i-1
可能有些同学认为是巧合,我们用题目开头的那个例子来实验:
原式:
(
a
2
−
a
1
)
−
(
a
3
−
a
1
)
−
(
a
3
−
a
2
)
−
(
a
4
−
a
1
)
−
(
a
4
−
a
2
)
−
(
a
4
−
a
3
)
(a_2-a_1)-(a_3-a_1)-(a_3-a_2)-(a_4-a_1)-(a_4-a_2)-(a_4-a_3)
(a2−a1)−(a3−a1)−(a3−a2)−(a4−a1)−(a4−a2)−(a4−a3)
拆括号和排序:
a
2
−
a
1
+
a
1
+
a
2
+
a
1
+
a
2
+
a
3
−
a
3
−
a
3
−
a
4
−
a
4
−
a
4
a_2-a_1+a_1+a_2+a_1+a_2+a_3-a_3-a_3-a_4-a_4-a_4
a2−a1+a1+a2+a1+a2+a3−a3−a3−a4−a4−a4
看到了吗,加上从二开始的每个点的前缀和,再减去从三开始的每个点成当前i减1。
代码2:100分
#include<bits/stdc++.h>
using namespace std;
long long n,a[500010],vh[500010];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
vh[i]=vh[i-1]+a[i];
}
long long ans=a[2]-a[1];
for(int i=2;i<n;++i) ans+=vh[i];
for(int i=3;i<=n;++i) ans-=a[i]*(i-1);
cout<< ans << endl;
return 0;
}
T2-二进制异或
题目描述
给定两个只包含 0,1 的二进制数字,请你求出两个数字异或以后的二进制值是多少?(两个数字异或是指其二进制形式下按位做异或运算)
所谓异或,是指不同值异或结果为1,相同值异或结果为0,即:
0
⊕
0
=
0
0⊕0=0
0⊕0=0
0
⊕
1
=
1
0⊕1=1
0⊕1=1
1
⊕
0
=
1
1⊕0=1
1⊕0=1
1
⊕
1
=
0
1⊕1=0
1⊕1=0
输入格式
输入共两行,每行一个二进制数字
输出格式
输出共一行,一个二进制数字表示答案
数据范围
对于
50
%
50 \%
50%的数据,输入的二进制数字长度不超过7位
对于
100
%
100 \%
100%的数据,输入的二进制数字长度不超过100位
注意:输入的两个二进制数字长度可能不相同
样例数据
输入1:
1101
1110
输出1:
11
说明1:
按位异或的答案为0011,去掉前导0后即为11
输入2:
100101
1010
输出2:
101111
分析
这题是要我们求两个二进制数异或后的值。首先我们需要知道异或是怎么算的。题目中已经给出:相同为0,不同为1。因此我们可以用两个字符串
s
1
s_1
s1 和
s
2
s_2
s2 来表示两个二进制数, 然后用第三个字符串
s
s
s 记录最终异或的答案。注意:输入的两个二进制数字长度可能不相同!此时应该在较短的数前面补0。同时,异或时要从数的末尾开始一一匹配,然后进行运算,不是从头开始的!!! 最后,因为有前导0的存在,我们需要写一个删除前导0的while语句,最后输出 ,完事儿,下班!。
OK!上代码!
#include <bits/stdc++.h>
#define int long long
using namespace std;
string s1, s2, s;
signed main(){
cin >> s1 >> s2;
int l1 = s1.size(), l2 = s2.size();
if (l1 > l2){//帮短的数补0使它变长
for (int i = 1; i <= l1 - l2; ++i)
s2 = '0' + s2;
l2 = l1;
}
else if (l1 < l2){
for (int i = 1; i <= l2 - l1; ++i)
s1 = '0' + s1;
l1 = l2;
}
for (int i = 0; i < l1; ++i){//打工仔异或工作中
if (s1[i] == s2[i])
s += '0';
else
s += '1';
}
int l = s.size();
while (s[0] == '0'){//删除影响我们AC的前导0
for (int i = 0; i < l - 1; ++i)
s[i] = s[i + 1];
l--;
}
if (s == '')//判断一下s是否是空的
cout << 0 << endl;
else {//正常情况,正常输出,完美!
for (int i = 0; i < l; ++i)
cout << s[i];
cout << endl;
}
return 0;
}
等等,还没完呢!如果
s
s
s 里面都是0该咋办?一删不就空了吗!还咋输出啊?!此时应该请特判同学出场······如果
s
s
s 是空的,那么就输出0。(值得一提的是,我在前一个程序上并没有写特判,但我还是AC了,所以说,这次数据是真的良心)
T3-颜文字
内存限制: 256 Mb
时间限制: 1000 ms
题目描述
聊天对话中会用一些符号表示心情。:-)
表示好心情,:-(
表示坏心情。给定一串字符序列表示用户的聊天记录,用户的心情指数定义为其中好心情数量减去坏心情数量。
请统计并输出用户的心情指数。
输入格式
- 一串字符表示聊天记录
输出格式
- 单个整数表示用户的心情指数
数据范围
设 n n n 表示输入序列的单词数量,则
- 1 ≤ n ≤ 10000 1≤n≤10000 1≤n≤10000
这道题应该用一个while循环,以此判断是否有对话读入。每次读入一个字符串,用cin,读入到空格自动终止,如果是:-),则开心指数+1,反之,坏心情+1,随后输出相减结果。
#include <bits/stdc++.h>
using namespace std;
int h=0,s=0;
string st;
int main(){
while(cin>>st){
if(st==":-)") h++;
if (st==":-(") s++;
}
cout<<h-s<<endl;
return 0;
}
T4-选取子段(二)
题意
给定一个长n的序列,从中截取一个任意长的字段,确保其间每个元素相同,问一共能截取多少这样的字段。
分析
首先,所有子段都是连续的,所以可以先将字符串分割成元素相同的几段。
然后,计算每个子段可以分成几段,最后累加起来输出
假设有一个长m的字段:
1、分成一个一组,一共分成n组。
2、分成两个一组,一共分成(n - 1)组
……
n、分成n个一组,一共分成1组
共:
n
+
(
n
−
1
)
+
(
n
−
2
)
+
…
…
+
2
+
1
=
(
1
+
n
)
×
n
÷
2
n+(n-1)+(n-2)+……+2+1 = (1+n) \times n \div 2
n+(n−1)+(n−2)+……+2+1=(1+n)×n÷2组
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
long long n, a[MAXN], b[MAXN], num = 1;
int main(){
cin >> n;
cin >> a[1];
b[1]++;
for (int i = 2; i <= n; i++){
cin >> a[i];
if (a[i] == a[i - 1])//判断是否与上一个元素相同
b[num]++;
else{
//否则新开一组记录
num++;
b[num]++;
}
}
long long ans = 0;
for (int i = 1; i <= num; i++){
ans += (1 + b[i]) * b[i] / 2;//计算组数
}
cout << ans << endl;
return 0;
}
T5-观光单车
题目描述
已知公园内有出借观光单车,每辆单车限坐两人,且两人体重之和不能超过观光单车的限重 T T T。
小爱带领班级 n n n 位同学共同前往借车浏览风景,已知每位同学的体重分别为 w 1 , w 2 , , . . . , w n w_1,w_2, ,...,w_n w1,w2,,...,wn ,请问该班级最少借多少辆车,才能满足每个人的乘坐需求。
输入格式
输入共两行:
第一行,两个正整数
n
,
T
n,T
n,T
第二行,
n
n
n个正整数
w
1
,
w
2
,
,
.
.
.
,
w
n
w_1,w_2, ,...,w_n
w1,w2,,...,wn
输出格式
输出一个正整数,表示最少租借单车的数量。
数据范围
对于 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 ≤ w i ≤ T ≤ 1 0 4 1≤n≤10^5 ,1≤w_i≤T≤10^4 1≤n≤105,1≤wi≤T≤104
样例数据
输入:
7 50
15 41 32 42 27 25 19
输出:
5
分析:这题可以用贪心来写,首先先排完序,然后第一与最后比较,如果小于T,那就说明可以做一组,右指针往左一位,左指针往右一位。如果大于了,你想想,左面的数越往后指,数越大,小的都大了,大的怎么比,说明右面只能单独做一辆车,右指针单独往左移
代码:100分
#include<bits/stdc++.h>
using namespace std;
long long n,t,a[100010],ans=0;
int main(){
cin>>n>>t;
for(int i=1;i<=n;++i) cin>>a[i];
stable_sort(a+1,a+n+1);
long long l=1,r=n;
while(l<=r){
if(a[l]+a[r]<=t){//小于时
ans++,l++,r--;//一起移
}else{
ans++,r--;//当不一样时,大的单独一辆,右指针单独往左移
}
}
cout<< ans << endl;
return 0;
}