题目描述
循环数组 ,长度为 ,若 和 互质,则删去 ,从循环数组第一个元素开始遍历,对于所有 ,每次只能删去后面的一个元素,遍历数组直到没有元素可删,求删除元素的顺序
示例
输入:用例数 , 组数组长度 和数组元素 ,其中
5
5
5 9 2 10 15
6
1 2 4 2 4 2
2
1 2
1
1
1
2
输出:被删除元素个数,以及它们的的索引 ,按删除顺序排列
2 2 3
2 2 1
2 2 1
1 1
0
分析
一开始我的解法就是按照我所解析的题干那样,建立一个循环链表去循环遍历,每次记录本次循环中被删除元素的个数,直到这个个数为0,当时将题目理解到这一步就理所当然地这么做了,完全没有想过这样做的复杂度有多么恐怖,于是理所当然的超时了。所以做题前一定要看数据范围,根据数据范围选择相应复杂度的算法,这一点非常重要!(又忘了,气得想扇自己两耳光),达到 量级的题的复杂度最高 。
这一题的核心是删除元素,如果不实际删除元素,而只是用标志位标记,那么每次都要在已经删除的元素中耗费大量的判断时间,所以必须用删除代价小的数据结构来存储元素,显然就是链表。一开始我是自己写了一个单向循环链表,后来参考别人的代码,知道了 中 底层实现就是双向循环链表,所以可以直接拿过来用。但是用了List需要注意到删除元素后元素索引会有所变动,所以一定在事先保存所有元素的索引。
然后,可以用一个队列,保存所有可能的元素对{a, b},满足 gcd(a, b) == 1,可以将其记为类型A。我们事先将所有相邻的元素对都加入队列中,之后每次将队首弹出直至队列为空,当且仅当队首的两个元素还在数组中,且构成A类元素对时,我们将队首第二个元素从数组中删除,将其索引加入结果数组中,且寻找b在数组中的下一元素c,将{a, c}添加到队尾。这样做的复杂度是
代码
#include <bits/stdc++.h>
using namespace std;
inline int gcd(int a,int b) {
int r;
while(b > 0) {
r = a%b;
a = b;
b = r;
}
return a;
}
void solve(list<pair<int,int>>& L, int n) {
queue<pair<int, int>> Q;
for(int i = 0; i < n-1; i++) {
Q.emplace(i, i+1);
}
Q.emplace(n-1, 0);
typedef list<pair<int,int>>::iterator node;
vector<node> M(n);
int idx = 0;
for(auto p = L.begin(); p != L.end(); p++) {
M[idx++] = p;
}
vector<int> removed;
while(!Q.empty()) {
int a = Q.front().first;
int b = Q.front().second;
Q.pop();
if(M[a] == L.end() || M[b] == L.end()) continue;
int g = M[a]->second, h = M[b]->second;
if(gcd(g, h) == 1) {
removed.emplace_back(M[b]->first);
L.erase(M[b]);
M[b] = L.end(); // 表示已经不在数组中
if(a == b) break;
auto u = M[a]; u++;
if(u == L.end()) u = L.begin();
Q.emplace(a, u->first);
}
}
cout << removed.size();
for(auto& x : removed) {
cout << " " << (x+1);
}
cout << '\n';
}
int main() {
int t; cin >> t;
list<pair<int,int>> L;
while(t--) {
int n; cin >> n;
L.clear();
for(int i = 0; i < n; i++) {
int x; cin >> x;
L.emplace_back(i, x);
}
solve(L, n);
}
}