寻找满足条件的最优子序列
阿里算法岗 0530笔试 第三题
题目内容
给定长度为 nnn 的数组 aaa,小C想选出一个长度为 kkk 的子序列。
所选子序列需满足相邻两个元素的 gcdgcdgcd 不为 111。具体地,若选取下标序列 i1<i2<⋯<iki_1 < i_2 < \dots < i_ki1<i2<⋯<ik,则需要满足:gcd(aij,aij+1)>1gcd(a_{i_j}, a_{i_{j+1}}) > 1gcd(aij,aij+1)>1(1≤j<k1 \le j < k1≤j<k)。
在所有满足上述条件的子序列中,求最大元素的最小可能取值。
如果不存在满足条件的子序列,则输出 −1-1−1。
【名词解释】
- 子序列:从原序列中删除任意个(可以为零)元素得到的新序列。
- gcdgcdgcd:即最大公因数,指两个整数共有约数中最大的一个。例如,gcd(12,30)=6gcd(12, 30) = 6gcd(12,30)=6。
输入描述
第一行输入两个整数 n,kn, kn,k(1≤k≤n1 \le k \le n1≤k≤n;2≤n≤2×1052 \le n \le 2 \times 10^52≤n≤2×105),分别表示数组长度和所选子序列长度。
第二行输入 nnn 个整数 a1,a2,...,ana_1, a_2, \dots, a_na1,a2,...,an(1≤ai≤1091 \le a_i \le 10^91≤ai≤109),表示数组元素的值。
输出描述
输出一个整数,表示满足条件的子序列中,最大元素的最小可能取值。
如果不存在满足条件的子序列,则输出 −1-1−1。
样例1
输入
5 3
2 4 3 9 6
输出
6
说明
- 可选下标 1,2,51, 2, 51,2,5 对应元素 {2,4,6}\{2, 4, 6\}{2,4,6},此时 gcd(2,4)=2>1gcd(2, 4) = 2 > 1gcd(2,4)=2>1,gcd(4,6)=2>1gcd(4, 6) = 2 > 1gcd(4,6)=2>1,最大值为 666。
- 可知没有更小的最大值,因此输出 666。
样例2
输入
5 2
5 7 11 13 17
输出
-1
说明
在这个样例中,数组中任意两个元素的 gcdgcdgcd 均为 111,无法选出符合条件的子序列,因此输出 −1-1−1。
题解
思路
二分 + dp
- 预处理每个数的不同质因子,可以先使用
质数筛求出值域中所有质数,在求出每个数的不同质因子。 - 根据
a的最大值和最小值确定二分的边界left = min(a), right = max(a) - 二分枚举确定结果,每次枚举
mid = (left + right) / 2进行check(mid), 直到left > right结束true: 更新ans = mid, right = mid - 1false: 更新left = mid + 1
- check函数的作用为
只允许使用 a[i] <= mid 的数,是否能够选出一个长度至少为k的子序列,使得相邻元素 gcd > 1。使用dp进行求解- 定义
best,含义为当前处理到的位置之前,以某个含有质因子p的数结尾的最长合法子序列长度。 - 状态转移:
- 对于当前数
a[i], 它的质因子是p1,p2.p3..., 同时gcd(x,y) > 1=> x 和y有公共质因子,所有当前数可以接在任何含有这些质因子的链后面。加入当前数之后能够组成最长链长度就为dp = max(best[p] + 1)。 - 对当前数的所有质因子都更新
best[p] = dp
- 对于当前数
- 按照上述逻辑如果存在best§ >= k 说明成立返回true,否则返回false
- 定义
C++
cpp
#include<bits/stdc++.h>
using namespace std;
int n, k;
vector<int> a;
// 预筛质数
vector<int> primes;
// 每个位置对应的不同质因子
vector<vector<int>> factors;
// 质数筛
void initPrimes() {
const int LIM = 31623; // 大于sqrt(10^9) 的最小整数;
vector<bool> isPrime(LIM + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= LIM; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= LIM; j += i) {
isPrime[j] = false;
}
}
}
for (int i = 2; i <= LIM; i++) {
if (isPrime[i]) {
primes.push_back(i);
}
}
}
// 找出所有数的质因子
void buildFactors() {
factors.resize(n);
for (int i = 0; i < n; i++) {
int x = a[i];
for (int p : primes) {
if (1LL * p * p > x) {
break;
}
if (x % p == 0) {
factors[i].push_back(p);
while (x % p == 0) {
x /= p;
}
}
}
// 由于 p * p 退出
if (x > 1) {
factors[i].push_back(x);
}
}
}
// 检验mid是否可行
bool check(int mid) {
// 以p为结尾最大链
vector<int> best(31624, 0);
int longest = 0;
for (int i = 0; i < n; i++) {
if (a[i] > mid) {
continue;
}
int dp = 1;
for (int p : factors[i]) {
dp = max(dp, best[p] + 1);
}
for (int p : factors[i]) {
best[p] = dp;
}
longest = max(longest, dp);
if (longest >= k) {
return true;
}
}
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
a.resize(n);
int right = 0;
int left = INT_MAX;
for (int i = 0; i < n; i++) {
cin >> a[i];
right = max(right, a[i]);
left = min(left, a[i]);
}
// 特判1
if (k == 1) {
cout << left << endl;
return 0;
}
initPrimes();
buildFactors();
int ans = -1;
while (left <= right) {
int mid = (left + right) >> 1;
if (check(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
cout << ans;
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static int n, k;
static int[] a;
static List<Integer> primes = new ArrayList<>();
static List<List<Integer>> factors;
// 质数筛
static void initPrimes() {
int LIM = 31623;
boolean[] isPrime = new boolean[LIM + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= LIM; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= LIM; j += i) {
isPrime[j] = false;
}
}
}
for (int i = 2; i <= LIM; i++) {
if (isPrime[i]) primes.add(i);
}
}
// 分解质因子
static void buildFactors() {
factors = new ArrayList<>();
for (int i = 0; i < n; i++) factors.add(new ArrayList<>());
for (int i = 0; i < n; i++) {
int x = a[i];
for (int p : primes) {
if (1L * p * p > x) break;
if (x % p == 0) {
factors.get(i).add(p);
while (x % p == 0) x /= p;
}
}
if (x > 1) factors.get(i).add(x);
}
}
static boolean check(int mid) {
int[] best = new int[31624];
int longest = 0;
for (int i = 0; i < n; i++) {
if (a[i] > mid) continue;
int dp = 1;
for (int p : factors.get(i)) {
dp = Math.max(dp, best[p] + 1);
}
for (int p : factors.get(i)) {
best[p] = dp;
}
longest = Math.max(longest, dp);
if (longest >= k) return true;
}
return false;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] first = br.readLine().split(" ");
n = Integer.parseInt(first[0]);
k = Integer.parseInt(first[1]);
a = new int[n];
String[] arr = br.readLine().split(" ");
int left = Integer.MAX_VALUE, right = 0;
for (int i = 0; i < n; i++) {
a[i] = Integer.parseInt(arr[i]);
left = Math.min(left, a[i]);
right = Math.max(right, a[i]);
}
if (k == 1) {
System.out.println(left);
return;
}
initPrimes();
buildFactors();
int ans = -1;
while (left <= right) {
int mid = (left + right) >> 1;
if (check(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
System.out.println(ans);
}
}
python
python
import sys
input = sys.stdin.readline
n, k = map(int, input().split())
a = list(map(int, input().split()))
# 质数筛
LIM = 31623
is_prime = [True] * (LIM + 1)
is_prime[0] = is_prime[1] = False
p = 2
while p * p <= LIM:
if is_prime[p]:
for j in range(p * p, LIM + 1, p):
is_prime[j] = False
p += 1
primes = [i for i in range(2, LIM + 1) if is_prime[i]]
# 分解质因子
factors = [[] for _ in range(n)]
for i in range(n):
x = a[i]
for p in primes:
if p * p > x:
break
if x % p == 0:
factors[i].append(p)
while x % p == 0:
x //= p
if x > 1:
factors[i].append(x)
def check(mid):
best = [0] * 31624
longest = 0
for i in range(n):
if a[i] > mid:
continue
dp = 1
for p in factors[i]:
dp = max(dp, best[p] + 1)
for p in factors[i]:
best[p] = dp
longest = max(longest, dp)
if longest >= k:
return True
return False
left, right = min(a), max(a)
if k == 1:
print(left)
sys.exit()
ans = -1
while left <= right:
mid = (left + right) // 2
if check(mid):
ans = mid
right = mid - 1
else:
left = mid + 1
print(ans)
javascript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let lines = [];
rl.on("line", (line) => {
lines.push(line.trim());
});
rl.on("close", () => {
let [n, k] = lines[0].split(" ").map(Number);
let a = lines[1].split(" ").map(Number);
let left = Math.min(...a);
let right = Math.max(...a);
const LIM = 31623;
// 质数筛
let isPrime = new Array(LIM + 1).fill(true);
isPrime[0] = isPrime[1] = false;
for (let i = 2; i * i <= LIM; i++) {
if (isPrime[i]) {
for (let j = i * i; j <= LIM; j += i) {
isPrime[j] = false;
}
}
}
let primes = [];
for (let i = 2; i <= LIM; i++) {
if (isPrime[i]) primes.push(i);
}
// 分解质因子
let factors = Array.from({ length: n }, () => []);
for (let i = 0; i < n; i++) {
let x = a[i];
for (let p of primes) {
if (p * p > x) break;
if (x % p === 0) {
factors[i].push(p);
while (x % p === 0) x /= p;
}
}
if (x > 1) factors[i].push(x);
}
function check(mid) {
let best = new Array(31624).fill(0);
let longest = 0;
for (let i = 0; i < n; i++) {
if (a[i] > mid) continue;
let dp = 1;
for (let p of factors[i]) {
dp = Math.max(dp, best[p] + 1);
}
for (let p of factors[i]) {
best[p] = dp;
}
longest = Math.max(longest, dp);
if (longest >= k) return true;
}
return false;
}
let ans = -1;
while (left <= right) {
let mid = (left + right) >> 1;
if (check(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
console.log(ans);
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
var n, k int
var a []int
var primes []int
var factors [][]int
func initPrimes() {
LIM := 31623
isPrime := make([]bool, LIM+1)
for i := 2; i <= LIM; i++ {
isPrime[i] = true
}
for i := 2; i*i <= LIM; i++ {
if isPrime[i] {
for j := i * i; j <= LIM; j += i {
isPrime[j] = false
}
}
}
for i := 2; i <= LIM; i++ {
if isPrime[i] {
primes = append(primes, i)
}
}
}
func buildFactors() {
factors = make([][]int, n)
for i := 0; i < n; i++ {
x := a[i]
for _, p := range primes {
if p*p > x {
break
}
if x%p == 0 {
factors[i] = append(factors[i], p)
for x%p == 0 {
x /= p
}
}
}
if x > 1 {
factors[i] = append(factors[i], x)
}
}
}
func check(mid int) bool {
best := make([]int, 31624)
longest := 0
for i := 0; i < n; i++ {
if a[i] > mid {
continue
}
dp := 1
for _, p := range factors[i] {
if best[p]+1 > dp {
dp = best[p] + 1
}
}
for _, p := range factors[i] {
best[p] = dp
}
if dp > longest {
longest = dp
}
if longest >= k {
return true
}
}
return false
}
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Fscan(reader, &n, &k)
a = make([]int, n)
left := int(1<<31 - 1)
right := 0
for i := 0; i < n; i++ {
fmt.Fscan(reader, &a[i])
if a[i] < left {
left = a[i]
}
if a[i] > right {
right = a[i]
}
}
if k == 1 {
fmt.Println(left)
return
}
initPrimes()
buildFactors()
ans := -1
for left <= right {
mid := (left + right) >> 1
if check(mid) {
ans = mid
right = mid - 1
} else {
left = mid + 1
}
}
fmt.Println(ans)
}