zpapl   6년 전

disjoint set으로 문제를 해결하려고 할 때,

각 원소의 root를 가지고 있는 배열을 선언했을 때

20퍼??정도에서 메모리 초과가 발생하네요, 10,000,000개의 배열을 사용해야하니 충분히 메모리 초과가 날 것같긴한데

어떻게 해결해야 할가요??

djm03178   6년 전

1717번 문제인가요? n이 최대 천만이 아니고 백만이라 전부 선언해도 괜찮아요. 천만이라고 해도 int형이면 40MB 정도라서 초과는 안 날 텐데... 코드에서 원인을 찾아야겠죠.

chogahui05   6년 전

어떻게 짜셨는지 모르겠는데..

int find(int a)
{
    if(p[a]==-1) return a;
    return find(p[a]);
}

요런 식으로 짜셨다면..

최대 100만까지 호출이 되어버릴 수 있어서 시간초과 + 메모리 초과 날 거 같네요.
예를 들어서 100만의 부모가 99만9999고 99만 9999번 부모가 99만 9998번이고
...
이런 식으로 계속 올라가면 말이죠.

일단 풀 소스를 보여주세요.

zpapl   6년 전


#include <iostream>
#include <vector>
#include <string>
using namespace std;

vector<int> parents;
int find(int n) {
if (parents[n] == -1) return n;
parents[n] = find(parents[n]);
return parents[n];
}

void merge(int a, int b) {
if (a == b) return;
a = find(a);
b = find(b);
parents[b] = a;
}

string is_equls_root(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return string("YES");
else return string("NO");
}

int main() {
int n, m;

cin >> n >> m;

parents = vector<int>(n + 1, -1);

for (int i = 0; i < m; i++) {
int operation, a, b;

cin >> operation >> a >> b;

if (operation == 0) {
merge(a, b);
}
else {
cout << is_equls_root(a, b) << endl;
}
}
}

zpapl   6년 전

위의 소스로 돌렸을 때 메모리초과가 발생합니다 ㅠ

djm03178   6년 전

한 방향으로 일방적으로 붙이면 마치 이진 탐색 트리에 정렬된 원소를 넣는 것처럼 트리가 높아져서 find 시에 재귀호출이 지나치게 많이 일어날 수 있습니다.

그래서 union find를 할 때는 양쪽의 균형이 맞도록 높이가 낮은 쪽이 높이가 높은 쪽에 연결되도록 구현을 하는 것이 좋습니다.

chogahui05   6년 전

rank까지는 잘 모르겠고..

유니온 파인드를 구현을 잘 하셨다면 find 과정에서 경로 압축을 합니다..

그러니까 다른 문제인 거 같은데..

자세히 보니까 merge 함수가 이상한데요?

예를 들어서 원래 1의 부모가 7이였고 3의 부모도 7이였다고 칩시다.

merge(1,3)을 하는 경우. 이 명령은 무시되어야 하는데. 님 코드에 따르면

1하고 3하고 다르니까 a=find(a)에서 a가 7 b도 7이 되네요.

그 결과 parent[7] = 7이 되는데. 이 상태에서 find(7)을 호출하면 무한루프가 돌지 않을까요?

zpapl   6년 전

두 분께 우선 감사드립니다.

chogahui05님 말씀대로 예외처리를 이상하게 해서 스택오버플로우 때문에 메모리초과를 받은 것 같네요

그래서 예외처리를 재대로하니, 메모리초과문제는 해결되었고 시간 이제 30퍼에서 시간초과문제가 발생해서
시간복잡도는 find()연산에 따라 달라지니 chogahui05님 말씀처럼 높이(?)가 더 높은 곳에 합칠 수 있도록(저는 사실 자식의 개수)했는데도 잘 안풀리네요ㅠㅠ

#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> parents;
int find(int n) {
if (parents[n] < 0) return n;
parents[n] = find(parents[n]);
return parents[n];
}
void merge(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return;
if (parents[a] < parents[b]) {
parents[a] += parents[b];
parents[b] = a;
}
else {
parents[b] += parents[a];
parents[a] = b;
}
}
string is_equls_root(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return string("YES");
else return string("NO");
}
int main() {
int n, m;
cin >> n >> m;
parents = vector<int>(n + 1, -1);
for (int i = 0; i < m; i++) {
int operation, a, b;
cin >> operation >> a >> b;
if (operation == 0) {
merge(a, b);
}
else {
cout << is_equls_root(a, b) << endl;
}
}
}





djm03178   6년 전

높은 쪽에 낮은 쪽 합치는 건 제가 한 말인데요 ㅠ

그리고 시간 초과가 뜨는 건 입출력이 느려서 그래요.

1. ios_base::sync_with_stdio(0); 를 하고 시작하면 cin, cout이 stdin, stdout과 별개의 버퍼를 가지고 동작할 수 있어 꽤 빨라집니다.

2. endl은 매우 매우 느립니다. endl은 단순 개행이 아니고, 개행을 함과 동시에 버퍼를 비우는 역할을 하기 때문에, endl 대신에 '\n'을 쓰는 것이 훨씬 좋습니다.

3. cin.tie(NULL); 도 하고 시작해야 됩니다. cin은 기본값으로 cout에 묶여있어서 cin이 호출되면 항상 cout을 flush 하고 시작하거든요.

chogahui05   6년 전

merge 함수에서.. find(a)는 a의 root이고 find(b)는 b의 root이므로. a=find(a); b=find(b);를 수행한 후에는 a는 root_a, b는 root_b가 되겠죠.

그런데. parents[a]는 부모를 찾아가기 위해서 만들어 놓은 것입니다.

보시면 parents[a] = parents[a] + parents[b]를 해 놓으셨는데.

이러면 어떻게 될까요? 이상한 동작을 수행해 버립니다.

그러므로 parents[b] = a; 문장만 넣어주셔야 합니다.

zp 님의 의도는 최종적으로 b의 root (B)를 a의 root (A) 밑에 넣는 게 아닌가요?

djm03178   6년 전

이것들만 추가해서 내봤는데 32MS 뜹니다.

chogahui05   6년 전

히익?? 후덜덜..

djm03178   6년 전

그러게요, 저도 다시 보니 좀 이상하네요.

djm03178   6년 전

생각을 해 보니까...

처음에 parents는 모두 -1로 초기화 되어있고, merge를 할 때 서로 합쳐지는 두 노드는 모두 기존 트리의 root들이죠. 그런데 둘 다 음수라면, += 을 한 결과도 음수가 나오겠고요. 그렇다면 find를 할 때 문제는 안 되겠네요.

루트가 안 된 쪽은 정확하게 새로운 루트를 부모로 가지니, 다시 find를 해도 문제는 안 생기겠네요.

djm03178   6년 전

결국 이건 결과적으로 봤을 때, 트리의 높이 균형은 아니고 자식/자손 노드의 수가 적은 쪽이 많은 쪽에 붙게 될 것 같아요.

zpapl   6년 전

아 죄송합니닷ㅠ

djm03178님이 가르쳐주신대로 높이 균형을 맞출라고 했습니다만,

중간에 find()함수를 최적화하기 위해서 한번 검색에 사용된 노드는 부모의 자식으로 (승격?)시켜서 다시 검색할 땐 속도 향상(?)을 시키려고 했습니다.
그러다 보니 높이를 구할 방법이 생각은 안나고 자식의 개수가 많은 쪽에 붙이도록 구현했습니다 ㅋㅋ 혼동시켜드려 죄송합니다

두 분다 정말 감사합니다.

출력 시간 때문에 시간 초과 나는건 당황스럽네요 ㅋㅋ 감사합니다!!!

두 분중 한분이라도 답변안해주셨으면 저는 못풀었을 문제네요!


댓글을 작성하려면 로그인해야 합니다.