题目描述
这天,小明在组装齿轮。
他一共有 nnn 个齿轮,第 iii 个齿轮的半径为 rir_{i}ri, 他需要把这 nnn 个齿轮按一定顺序从左到右组装起来,这样最左边的齿轮转起来之后,可以传递到最右边的齿轮,并且这些齿轮能够起到提升或者降低转速(角速度)的作用。
小明看着这些齿轮,突然有 QQQ 个疑问: 能否按一定顺序组装这些齿轮使得最右边的齿轮的转速是最左边的齿轮的 qiq_{i}qi 倍?
输入格式
输入共 Q+2Q+2Q+2 行,第一行为两个正整数 n,Qn, Qn,Q, 表示齿轮数量和询问数量。
第二行为 nnn 个正整数 r1,r2,…,rnr_{1}, r_{2}, \ldots, r_{n}r1,r2,…,rn,表示每个齿轮的半径。
后面 QQQ 行,每行一个正整数 qiq_{i}qi 表示询问。
输出格式
QQQ 行,对于每个询问,如果存在至少一种组装方案满足条件,输出 YES
, 否则输出 NO
。
输入输出样例 #1
输入 #1
5 3
4 2 3 3 1
2
4
6
输出 #1
YES
YES
NO
说明/提示
【样例说明】
询问 111 方案之一:23341
。
询问 222 方案之一:42331
。
询问 333 没有方案。
【评测用例规模与约定】
对于 15%15 \%15% 的数据,保证 n,Q≤100n, Q \leq 100n,Q≤100;
对于 30%30 \%30% 的数据,保证 n,Q≤2000n, Q \leq 2000n,Q≤2000;
对于 100%100 \%100% 的数据,保证 n≥2,n,Q≤2×105;ai,qi≤2×105n\ge 2,n, Q \leq 2 \times 10^{5} ; a_{i}, q_{i} \leq 2 \times 10^{5}n≥2,n,Q≤2×105;ai,qi≤2×105。
蓝桥杯 2022 国赛 B 组 I 题。
问题本质分析
这道题本质上是关于齿轮转速比的数学问题。首先需要理解齿轮传动的物理原理:两个啮合的齿轮,转速与半径成反比。即如果齿轮A半径为r1,齿轮B半径为r2,当A带动B转动时,B的转速是A的 r1/r2 倍。
对于多个齿轮组成的序列,整体转速比是相邻齿轮转速比的乘积。例如齿轮序列r1, r2, r3的转速比为 (r1/r2) * (r2/r3) = r1/r3。
关键观察
通过分析可以得出以下重要结论:
- 对于n个齿轮组成的序列,最终转速比等于第一个齿轮半径与最后一个齿轮半径的比值
- 中间齿轮的半径会在转速比计算中被约分抵消
- 因此,问题转化为:是否存在两个齿轮r_a和r_b,使得 r_a/r_b = q_i,并且可以在它们之间插入其他齿轮
解题思路核心
代码采用了预处理+查询的策略,主要步骤如下:
-
统计齿轮半径出现次数:使用cnt数组统计每个半径出现的频率
-
预处理可行转速比:
- 首先处理转速比为1的特殊情况:如果存在至少两个相同半径的齿轮,那么转速比可以为1
- 对于转速比i>1的情况,检查是否存在两个齿轮半径j和ij,使得cnt[j]和cnt[ij]都大于0
- 这是因为如果存在半径为j和ij的齿轮,那么它们的转速比为 (ij)/j = i
-
处理查询:对于每个查询q_i,直接查询预处理好的ans数组
算法优化说明
-
时间复杂度分析:
- 统计半径出现次数:O(n)
- 预处理ans数组:O(max_r * log(max_r)),其中max_r是齿轮半径的最大值(200000)
- 处理查询:O(q)
- 总体时间复杂度:O(n + max_r * log(max_r) + q),可以高效处理题目给定的数据规模
-
核心优化点:
- 利用"转速比等于首尾齿轮半径比"的性质,将问题转化为寻找特定半径组合
- 通过预处理将查询复杂度降为O(1)
- 使用数组直接存储预处理结果,加快查询速度
样例解释
以样例输入为例:
- 齿轮半径为[4,2,3,3,1]
- 统计cnt数组:cnt[1]=1, cnt[2]=1, cnt[3]=2, cnt[4]=1
- 对于查询2:寻找j和2j,当j=2时,2j=4,cnt[2]和cnt[4]都为1,所以ans[2]=true
- 对于查询4:寻找j和4j,当j=1时,4j=4,cnt[1]和cnt[4]都为1,所以ans[4]=true
- 对于查询6:找不到j和6j都存在的情况,所以ans[6]=false
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,q,cnt[200005];
bool ans[200005];
signed main()
{
cin>>n>>q;
for(int i=0;i<n;++i){
int x;
cin>>x;
cnt[x]++;
}
for(int i=1;i<=200000;++i){//枚举倍数
if(cnt[i]>=2){
ans[1]=true;
break;
}
}
for(int i=2;i<=200000;++i){
for(int j=1;j<=200000/i;++j){
if(cnt[j]&&cnt[i*j]){
ans[i]=true;
break;
}
}
}
while(q--){
int k;
cin>>k;
if(ans[k]||(n==1&&k==1))cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}