题目:
来点gcd (nowcoder.com)
给定一个有 n个元素的多重集 S,有 m个询问,对于每个询问,给出一个整数 x,问是否能选择 S 的一个非空子集,满足这个子集的 gcd 等于x,当集合只有一个数时,设这个集合的 gcd 就等于这个数,gcd(x,y)的值为x,y的最大公约数。
解析:
刚开始我写,答案应该是没问题,点超时了。我的想法是,设立两个数组,一个数组存集合S的元素,另一个将集合S的元素两两循环搭配求gcd。显然耗时!
后来看题解,有一个地方没看懂:
// 计算每个可能 GCD 的值
for (int i = 1; i <= n; i++) {
int g = 0;
for (int j = i; j <= n; j += i) {
if (vis[j]) {
g = __gcd(g, j);
}
}
if (g == i) {
res[i] = true;
}
}
- 遍历每个可能的 GCD 值 i。
- 对于每个 i,计算所有 i 的倍数中的 GCD。
- 如果计算出的 GCD 值等于 i,则标记
res[i] = true
。
这里其实就是从1开始遍历,判断是否 i 能成为由另两个数组成的gcd,而 i 要能被gcd出来,那一定是要被i的倍数gcd出来,因此,这个判断条件就是判断第一个存放S的数组里面是否有 i 的倍数的这个元素。懂了之后,我又再改进了一下:
for (int i = 1; i <= n; i++)
{
int g = 0;
for (int j = i; j <= n; j += i)
{
if (arr1[j])
{
g = gcd(g, j);
if (g == i)
{
arr2[i] = 1;
break;
}
}
}
}
中途如果找到了,其实可以直接退出的,不用继续循环了。
全代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
using namespace std;
int arr1[1000000];
int arr2[1000000];
inline int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
int temp;
scanf("%d", &temp);
arr1[temp] = 1;
arr2[i] = 0;
}
for (int i = 1; i <= n; i++)
{
int g = 0;
for (int j = i; j <= n; j += i)
{
if (arr1[j])
{
g = gcd(g, j);
if (g == i)
{
arr2[i] = 1;
break;
}
}
}
arr1[i] = 0;
}
while (m--)
{
int num;
scanf("%d", &num);
if (arr2[num])printf("YES\n");
else printf("NO\n");
}
}
}
还有另外一种,干脆不设arr2[],直接输入一个要判断的数字再进行判断,这样其实就只要一重循环了(把原来的二重循环的外面的大循环去掉了)
源代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
using namespace std;
int arr1[1000000];
inline int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
arr1[i] = 0;
int temp;
scanf("%d", &temp);
arr1[temp] = 1;
}
while (m--)
{
int num;
scanf("%d", &num);
int g = 0;
for (int i = 1; i * num <= n; i++)
{
if (arr1[i * num])
{
g = gcd(g, i * num);
if (g == num)
{
printf("YES\n");
break;
}
}
}
if (g != num)printf("NO\n");
}
}
}
两种方法编译器所判定的时间差不多,第二种适合m比较小的情况,第一种则适合m比较大的情况。