闽江学院第二届算法集训队第二阶段第一周周小测题解
P1102 A-B 数对
题目背景
出题是一件痛苦的事情!
相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!
题目描述
给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N , C N,C N,C。
第二行, N N N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A − B = C A - B = C A−B=C 的数对的个数。
题解
对于这题,可以使用二分和哈希表算法
二分
对于 A − B = C A-B=C A−B=C 可以转换为 A = B + C A=B+C A=B+C 此时只需要枚举B的所有可能然后用二分查找找到 B + C B+C B+C 的范围。
参考代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2e5+10;
int num[N];
int main(){
int n,c;
long long ans=0;
scanf("%d %d",&n,&c);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
}
sort(num,num+n);///由于数组不一定是有序的,因此要先进行一次排序
for(int i=0;i<n;i++){
int l=0,r=n-1;
while(l<r){
int mid=(l+r)>>1;
if(num[mid]>=num[i]+c)r=mid;
else l=mid+1;
}
if(num[l]==num[i]+c){
int l2=0,r2=n-1;
while(l2<r2){
int mid=(l2+r2+1)>>1;
if(num[mid]<=num[i]+c)l2=mid;
else r2=mid-1;
}
ans+=l2-l+1;
}
}
printf("%lld",ans);
}
哈希表
与二分的思路类似,只不过在找数的时候使用哈希表来进行查找
参考代码
#include <iostream>
#include <cstring>
using namespace std;
const int N=300007,null=0x3f3f3f3f;
int h[N],cnt[N],num[N];
//查找x在hash表中的位置
int find(int x){
int t=(x%N+N)%N;
while(h[t]!=null&&h[t]!=x){
t++;
if(t==N)t=0;
}
return t;
}
int main() {
int n,c;
long long ans=0;
memset(h,0x3f,sizeof h);
scanf("%d %d",&n,&c);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
int x= find(num[i]);
h[x]=num[i];
cnt[x]++;//标记num[i]的数量
}
for(int i=0;i<n;i++){
int x= find(num[i]+c);
if(h[x]!=null){
ans+=cnt[x];
}
}
printf("%lld",ans);
}
P2004 领地选择
题目描述
作为在虚拟世界里统帅千军万马的领袖,小 Z 认为天时、地利、人和三者是缺一不可的,所以,谨慎地选择首都的位置对于小 Z 来说是非常重要的。
首都被认为是一个占地 C × C C\times C C×C 的正方形。小 Z 希望你寻找到一个合适的位置,使得首都所占领的位置的土地价值和最高。
输入格式
第一行三个整数 N , M , C N,M,C N,M,C,表示地图的宽和长以及首都的边长。
接下来 N N N 行每行 M M M 个整数,表示了地图上每个地块的价值。价值可能为负数。
输出格式
一行两个整数 X , Y X,Y X,Y,表示首都左上角的坐标。
题解
这题考查的是前缀和的应用
先对所给的矩阵求出二维前缀和,之后再枚举所有首都可以占领的位置,最后将所有位置的值取一个最大值即可
参考代码
#include <iostream>
using namespace std;
const int N=1010;
int s[N][N];
int main(){
int n,m,c;
scanf("%d %d %d",&n,&m,&c);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&s[i][j]);
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];
}
}
int x=0,y=0,max=-0x3f3f3f3f;//结果可能是负数所以把最大值赋值为负无穷
for (int i = 1; i <= n-c+1; ++i) {
for(int j = 1;j<=m-c+1;j++){
if(s[i+c-1][j+c-1]-s[i-1][j+c-1]-s[i+c-1][j-1]+s[i-1][j-1]>max){
max=s[i+c-1][j+c-1]-s[i-1][j+c-1]-s[i+c-1][j-1]+s[i-1][j-1];
x=i,y=j;
}
}
}
printf("%d %d",x,y);
}
P8772 [蓝桥杯 2022 省 A] 求和
题目描述
给定 n n n 个整数 a 1 , a 2 , ⋯ , a n a_{1}, a_{2}, \cdots, a_{n} a1,a2,⋯,an, 求它们两两相乘再相加的和,即
S = a 1 ⋅ a 2 + a 1 ⋅ a 3 + ⋯ + a 1 ⋅ a n + a 2 ⋅ a 3 + ⋯ + a n − 2 ⋅ a n − 1 + a n − 2 ⋅ a n + a n − 1 ⋅ a n S=a_{1} \cdot a_{2}+a_{1} \cdot a_{3}+\cdots+a_{1} \cdot a_{n}+a_{2} \cdot a_{3}+\cdots+a_{n-2} \cdot a_{n-1}+a_{n-2} \cdot a_{n}+a_{n-1} \cdot a_{n} S=a1⋅a2+a1⋅a3+⋯+a1⋅an+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an
输入格式
输入的第一行包含一个整数 n n n 。
第二行包含 n n n 个整数 a 1 , a 2 , ⋯ a n a_{1}, a_{2}, \cdots a_{n} a1,a2,⋯an 。
输出格式
输出一个整数 S S S,表示所求的和。请使用合适的数据类型进行运算。
题解
由于
S
=
a
1
⋅
a
2
+
a
1
⋅
a
3
+
⋯
+
a
1
⋅
a
n
+
a
2
⋅
a
3
+
⋯
+
a
n
−
2
⋅
a
n
−
1
+
a
n
−
2
⋅
a
n
+
a
n
−
1
⋅
a
n
S=a_{1} \cdot a_{2}+a_{1} \cdot a_{3}+\cdots+a_{1} \cdot a_{n}+a_{2} \cdot a_{3}+\cdots+a_{n-2} \cdot a_{n-1}+a_{n-2} \cdot a_{n}+a_{n-1} \cdot a_{n}
S=a1⋅a2+a1⋅a3+⋯+a1⋅an+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an
=
a
1
⋅
(
a
2
+
a
3
+
.
.
.
+
a
n
)
+
a
2
⋅
(
a
3
+
a
4
+
.
.
.
+
a
n
)
+
.
.
.
+
a
n
−
1
⋅
a
n
=a_1 \cdot (a_2+a_3+...+a_n)+a_2 \cdot (a_3+a_4+...+a_n)+...+a_{n-1} \cdot a_n
=a1⋅(a2+a3+...+an)+a2⋅(a3+a4+...+an)+...+an−1⋅an
所以可以求出所有数的和,然后对数组进行遍历,对于每个 a i a_i ai 先让 s s s 减去 a i a_i ai 再将得出的结果乘上 a i a_i ai 最后将所有的结果相加即可。
参考代码
#include <iostream>
using namespace std;
const int N=2e5+10;
int num[N];
int main(){
int n;
long long sum=0,res=0;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
sum+=num[i];
}
for(int i=0;i<n;i++){
sum-=num[i];
res+=sum*num[i];
}
printf("%lld",res);
}
P1009 [NOIP1998 普及组] 阶乘之和
题目描述
用高精度计算出 S = 1 ! + 2 ! + 3 ! + ⋯ + n ! S = 1! + 2! + 3! + \cdots + n! S=1!+2!+3!+⋯+n!( n ≤ 50 n \le 50 n≤50)。
其中 !
表示阶乘,定义为
n
!
=
n
×
(
n
−
1
)
×
(
n
−
2
)
×
⋯
×
1
n!=n\times (n-1)\times (n-2)\times \cdots \times 1
n!=n×(n−1)×(n−2)×⋯×1。例如,
5
!
=
5
×
4
×
3
×
2
×
1
=
120
5! = 5 \times 4 \times 3 \times 2 \times 1=120
5!=5×4×3×2×1=120。
输入格式
一个正整数 n n n。
输出格式
一个正整数 S S S,表示计算结果。
题解
这题考查的是高精度加法与高进度乘法的应用
这里分别给出高精度加法和乘法的模板
加法
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
乘法
// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
我们只需要套用这两个模板求出数的阶乘然后求和即可
参考代码
#include <iostream>
#include <vector>
using namespace std;
// C = A * b, A >= 0, b >= 0
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
int main(){
int n;
scanf("%d",&n);
vector<int>sum,mu;
mu.push_back(1);
for(int i=1;i<=n;i++){
mu= mul(mu, i);
sum= add(sum,mu);
}
for(int i=sum.size()-1;i>=0;i--){
printf("%d",sum[i]);
}
}
P1469 找筷子
题目描述
经过一段时间的紧张筹备,电脑小组的“RP 餐厅”终于开业了,这天,经理 LXC 接到了一个定餐大单,可把大家乐坏了!员工们齐心协力按要求准备好了套餐正准备派送时,突然碰到一个棘手的问题:筷子!
CX 小朋友找出了餐厅中所有的筷子,但遗憾的是这些筷子长短不一,而我们都知道筷子需要长度一样的才能组成一双,更麻烦的是 CX 找出来的这些筷子数量为奇数,但是巧合的是,这些筷子中只有一只筷子是落单的,其余都成双,善良的你,可以帮 CX 找出这只落单的筷子的长度吗?
输入格式
第一行是一个整数,表示筷子的数量 n n n。
第二行有 n n n 个整数,第 i i i 个整数表示第 i i i 根筷子的长度 a i a_i ai。
输出格式
输出一行一个整数表示答案。
题解
这题考查的是位运算中异或和的使用
异或和:相同为0不同为1
如$10010^11010=01000
对于本题假设有3根筷子为A,A,B。对A和A求异或和得出结果为0,由于任何数与0异或都为这个数本身,所以可以通过求前缀和的方式找到单独的筷子
参考代码
#include <iostream>
using namespace std;
int main(){
int n,x,ans=0;
scanf("%d",&n);
while (n--){
scanf("%d",&x);
ans^=x;
}
printf("%d",ans);
}