Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
View indices as vertices. Put an edge between two indices when their values are coprime. The problem asks for a matching of size .
If you can find one coprime pair , delete both endpoints. If another coprime pair survives, you are immediately done.
So the real subproblem is: given a chosen active set of indices, detect whether it contains any coprime pair without checking all pairs.
For a fixed value , if is the number of active values divisible by , then the number of active values coprime to is . That is just inclusion-exclusion wearing a fake mustache.
Take any edge . If no edge remains after deleting both endpoints, then every possible matching must use one edge through and one edge through . That leaves only the delete- and delete- worlds to test.
Think of the array as a graph.
Each index is a vertex. Put an edge between indices and when
Now the task is exactly to find two disjoint edges, meaning a matching of size .
The obvious all-pairs check is , which is dead on arrival. We need two ingredients:
Finding a coprime pair in an active set
Suppose some indices are currently active. For every square-free divisor , define as the number of active values divisible by .
For a fixed active value , the number of active values coprime to is
This is just Mobius inversion. A value contributes
which equals if , and otherwise. That is inclusion-exclusion over the prime factors of .
There is one small gotcha: if , this count includes the index itself, since . So for value , subtract before deciding whether a partner exists.
So findPair(active) works like this:
That last scan is fine because we only run this whole query a constant number of times. No quadratic nonsense sneaks in.
Using one edge as an anchor
First, find any coprime pair in the whole array. Call it .
If no such pair exists, there are no edges at all, so the answer is .
Now delete both and . If the remaining indices contain a coprime pair , then and are disjoint edges. Output them and go home.
The only interesting case is when deleting both and leaves no edge. Then every edge in the entire graph touches or .
If a matching of size still exists, it cannot use an edge completely outside , because there is no such edge. Therefore it must look like this:
where and are outside and .
So we test two asymmetric worlds.
Delete only . If we find an edge that does not use , then that edge is disjoint from , so we are done. Otherwise the found edge has form . Then we only need to know whether has a coprime neighbor outside . If yes, output and that edge from .
If that fails, delete only and do the symmetric check. This catches the annoying case where the first chosen used the only neighbor of , but had another possible neighbor. Tiny case split, big payoff.
Correctness proof
First, findPair(active) is correct. For any active index with value , the Mobius sum counts exactly the active indices whose values are coprime to . The self-count only happens when , and subtracting fixes it. Therefore findPair returns a pair exactly when the active set contains at least one coprime pair.
Now consider the main algorithm. If it cannot find even one initial edge , then the graph has no edges, so two disjoint edges are impossible.
If after deleting and there is an edge , then and are disjoint coprime pairs, so the output is valid.
Otherwise, no edge avoids both and . If a valid answer exists, its two disjoint edges must each touch one of and . Since the edges are disjoint, one must use and the other must use .
When we delete only , any found edge either avoids or has form . If it avoids , it is disjoint from and we are done. If it is , then the only way to pair it with an edge from is to find a coprime neighbor of outside , which the algorithm checks directly.
If that direct check fails, then all possible neighbors of outside are contained in the single vertex . Any remaining valid matching must therefore use and some edge from to a different vertex. The symmetric delete- check finds exactly that if it exists.
So every possible matching structure is covered. The algorithm only outputs after checking distinct indices and both gcd conditions, so every nonzero output is valid. If it outputs , all possible structures have been ruled out.
Complexity
For a value , the number of square-free divisors is , at most . Each findPair query costs
over its active indices, and we run only a constant number of queries per test case. Precomputing smallest prime factors up to is .
This comfortably fits the limits.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MAXA = 1000000;
int spf[MAXA + 1], cntDiv[MAXA + 1];
vector<vector<int>> sfDivs(MAXA + 1);
vector<char> built(MAXA + 1, 0);
void precompute() {
vector<int> primes;
primes.reserve(80000);
for (int i = 2; i <= MAXA; i++) {
if (!spf[i]) {
spf[i] = i;
primes.push_back(i);
}
for (int p : primes) {
long long v = 1LL * i * p;
if (v > MAXA || p > spf[i]) break;
spf[v] = p;
}
}
}
const vector<int>& getSquareFreeDivs(int x) {
if (built[x]) return sfDivs[x];
built[x] = 1;
auto& res = sfDivs[x];
res.push_back(1);
int y = x;
while (y > 1) {
int p = spf[y];
int sz = (int)res.size();
for (int i = 0; i < sz; i++) {
res.push_back(-res[i] * p);
}
while (y % p == 0) y /= p;
}
return res;
}
struct PairFinder {
const vector<int>& a;
vector<int> touched;
PairFinder(const vector<int>& a) : a(a) {}
pair<int, int> findPair(const vector<int>& ids) {
touched.clear();
for (int id : ids) {
for (int sd : getSquareFreeDivs(a[id])) {
int d = sd < 0 ? -sd : sd;
if (cntDiv[d] == 0) touched.push_back(d);
cntDiv[d]++;
}
}
int first = -1;
for (int id : ids) {
int ways = 0;
for (int sd : getSquareFreeDivs(a[id])) {
int d = sd < 0 ? -sd : sd;
ways += (sd > 0 ? cntDiv[d] : -cntDiv[d]);
}
if (a[id] == 1) ways--;
if (ways > 0) {
first = id;
break;
}
}
pair<int, int> ans = {-1, -1};
if (first != -1) {
for (int id : ids) {
if (id != first && gcd(a[first], a[id]) == 1) {
ans = {first, id};
break;
}
}
}
for (int d : touched) cntDiv[d] = 0;
return ans;
}
};
int main() {
setIO();
precompute();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<int> a(n);
for (int& x : a) cin >> x;
PairFinder finder(a);
vector<int> all(n);
iota(all.begin(), all.end(), 0);
auto base = finder.findPair(all);
if (base.first == -1) {
cout << 0 << '\n';
continue;
}
int u = base.first, v = base.second;
bool found = false;
array<int, 4> ans{};
auto save = [&](int p, int q, int r, int s) -> bool {
int b[4] = {p, q, r, s};
for (int i = 0; i < 4; i++) {
for (int j = 0; j < i; j++) {
if (b[i] == b[j]) return false;
}
}
if (gcd(a[p], a[q]) != 1) return false;
if (gcd(a[r], a[s]) != 1) return false;
ans = {p, q, r, s};
found = true;
return true;
};
vector<int> rest;
rest.reserve(n - 2);
for (int i = 0; i < n; i++) {
if (i != u && i != v) rest.push_back(i);
}
auto outside = finder.findPair(rest);
if (outside.first != -1) save(u, v, outside.first, outside.second);
auto checkSide = [&](int fixed, int other) {
if (found) return;
vector<int> ids;
ids.reserve(n - 1);
for (int i = 0; i < n; i++) {
if (i != fixed) ids.push_back(i);
}
auto e = finder.findPair(ids);
if (e.first == -1) return;
if (save(fixed, other, e.first, e.second)) return;
if (e.first == other || e.second == other) {
int x = (e.first == other ? e.second : e.first);
for (int y = 0; y < n; y++) {
if (y == fixed || y == other || y == x) continue;
if (gcd(a[fixed], a[y]) == 1) {
save(other, x, fixed, y);
return;
}
}
}
};
checkSide(u, v);
checkSide(v, u);
if (!found) {
cout << 0 << '\n';
} else {
cout << ans[0] + 1 << ' ' << ans[1] + 1 << ' ' << ans[2] + 1 << ' ' << ans[3] + 1 << '\n';
}
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MAXA = 1000000;
int spf[MAXA + 1], cntDiv[MAXA + 1];
vector<vector<int>> sfDivs(MAXA + 1);
vector<char> built(MAXA + 1, 0);
void precompute() {
vector<int> primes;
primes.reserve(80000);
for (int i = 2; i <= MAXA; i++) {
if (!spf[i]) {
spf[i] = i;
primes.push_back(i);
}
for (int p : primes) {
long long v = 1LL * i * p;
if (v > MAXA || p > spf[i]) break;
spf[v] = p;
}
}
}
const vector<int>& getSquareFreeDivs(int x) {
if (built[x]) return sfDivs[x];
built[x] = 1;
auto& res = sfDivs[x];
res.push_back(1);
int y = x;
while (y > 1) {
int p = spf[y];
int sz = (int)res.size();
for (int i = 0; i < sz; i++) {
res.push_back(-res[i] * p);
}
while (y % p == 0) y /= p;
}
return res;
}
struct PairFinder {
const vector<int>& a;
vector<int> touched;
PairFinder(const vector<int>& a) : a(a) {}
pair<int, int> findPair(const vector<int>& ids) {
touched.clear();
for (int id : ids) {
for (int sd : getSquareFreeDivs(a[id])) {
int d = sd < 0 ? -sd : sd;
if (cntDiv[d] == 0) touched.push_back(d);
cntDiv[d]++;
}
}
int first = -1;
for (int id : ids) {
int ways = 0;
for (int sd : getSquareFreeDivs(a[id])) {
int d = sd < 0 ? -sd : sd;
ways += (sd > 0 ? cntDiv[d] : -cntDiv[d]);
}
if (a[id] == 1) ways--;
if (ways > 0) {
first = id;
break;
}
}
pair<int, int> ans = {-1, -1};
if (first != -1) {
for (int id : ids) {
if (id != first && gcd(a[first], a[id]) == 1) {
ans = {first, id};
break;
}
}
}
for (int d : touched) cntDiv[d] = 0;
return ans;
}
};
int main() {
setIO();
precompute();
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<int> a(n);
for (int& x : a) cin >> x;
PairFinder finder(a);
vector<int> all(n);
iota(all.begin(), all.end(), 0);
auto base = finder.findPair(all);
if (base.first == -1) {
cout << 0 << '\n';
continue;
}
int u = base.first, v = base.second;
bool found = false;
array<int, 4> ans{};
auto save = [&](int p, int q, int r, int s) -> bool {
int b[4] = {p, q, r, s};
for (int i = 0; i < 4; i++) {
for (int j = 0; j < i; j++) {
if (b[i] == b[j]) return false;
}
}
if (gcd(a[p], a[q]) != 1) return false;
if (gcd(a[r], a[s]) != 1) return false;
ans = {p, q, r, s};
found = true;
return true;
};
vector<int> rest;
rest.reserve(n - 2);
for (int i = 0; i < n; i++) {
if (i != u && i != v) rest.push_back(i);
}
auto outside = finder.findPair(rest);
if (outside.first != -1) save(u, v, outside.first, outside.second);
auto checkSide = [&](int fixed, int other) {
if (found) return;
vector<int> ids;
ids.reserve(n - 1);
for (int i = 0; i < n; i++) {
if (i != fixed) ids.push_back(i);
}
auto e = finder.findPair(ids);
if (e.first == -1) return;
if (save(fixed, other, e.first, e.second)) return;
if (e.first == other || e.second == other) {
int x = (e.first == other ? e.second : e.first);
for (int y = 0; y < n; y++) {
if (y == fixed || y == other || y == x) continue;
if (gcd(a[fixed], a[y]) == 1) {
save(other, x, fixed, y);
return;
}
}
}
};
checkSide(u, v);
checkSide(v, u);
if (!found) {
cout << 0 << '\n';
} else {
cout << ans[0] + 1 << ' ' << ans[1] + 1 << ' ' << ans[2] + 1 << ' ' << ans[3] + 1 << '\n';
}
}
return 0;
}