·首先,理解一下题意,给定一个序列,对每个查询,输出该序列是否存在子序列的gcd(最大公约数)为查询所给的数。
·我们知道,对最开始的序列中的重复的数,其对gcd其实没有影响(对与其相等的数,gcd就是它自己,对与其不等的数,gcd也都是一样的);
·所以我们可以先把序列去重(以题上的{2, 2, 6, 6, 2, 1, 5})为例
{2, 2, 6, 6, 2, 1, 5} --> {2, 6, 1, 5}
·最终把序列里的所有数存在vis里
for (int i = 0; i < n; i ++ )
{
int num;
scanf("%d", &num);
vis[num] = 1;
}
·然后对于查询给出的数
·显然可能以其为gcd的数只有可能为它的倍数
·以只可能为倍数倍数这点,我们想到经典的“调和级数”遍历方式
·然后对原序列中的所有查询的数的倍数取gcd, 若gcd与查询的数相等,则说明存在子序列的gcd等于查询的数。(因为都是倍数,gcd最小只会到查询的数,我们知道一个序列的gcd是单调不增的,所以最后的gcd与查询的数相等必然有子序列的gcd为查询的数)
·所以可以预处理一遍1~n的所有数看是否满足条件存到res里
·最后查询的时候看每个数是否在res里即可
for (int i = 1; i <= n; i ++ )
{
int num_gcd = 0;
for (int j = i; j <= n; j += i)
{
if (vis[j]) num_gcd = gcd(j, num_gcd);
}
if (num_gcd == i) res[i] = 1;
}
·完整代码 复杂度O(nlogn + m)
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, m;
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
scanf("%d%d", &n, &m);
vector<int> vis(n + 10, 0);
vector<int> res(n + 10, 0);
for (int i = 0; i < n; i ++ )
{
int num;
scanf("%d", &num);
vis[num] = 1;
}
for (int i = 1; i <= n; i ++ )
{
int num_gcd = 0;
for (int j = i; j <= n; j += i)
{
if (vis[j]) num_gcd = gcd(j, num_gcd);
}
if (num_gcd == i) res[i] = 1;
}
for (int i = 0; i < m; i ++ )
{
int num;
scanf("%d", &num);
if (res[num]) puts("YES");
else puts("NO");
}
}
return 0;
}
·这里顺便解释一下为什么调和级数遍历的复杂度是O(nlogn)
·首先对
for (int i = 1; i <= n; i ++ )
{
for (int j = i; j <= n; j += i)
{
***
}
}
·显然这样遍历的复杂度为
·把n提出来得到
(后面乘的就是调和级数)
·所以有
·所以复杂度为O(nlogn)
(但是证明过程中有个小于号,所以其实会比O(nlogn) 还快不少)
·希望对大家有所帮助qwq

京公网安备 11010502036488号