※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 8310번 문제인 Riddle이다.
문제는 아래 링크를 확인하자.

https://www.acmicpc.net/problem/8310 

 

8310번: Riddle

In the first line of the standard input there are three integers: n (1 ≤ n ≤ 1,000,000), denoting the number of towns in Voldebyte's riddle, m (0 ≤ m ≤ 1,000,000), denoting the number of roads, and k (1 ≤ k ≤ n), denoting the number of count

www.acmicpc.net

각 i번째 town이 capital이면 true, 그렇지 않으면 false 값을 갖는 bool 변수 Ti를 생각해보자. 만약 문제의 조건들을 2-CNF로 표현이 가능하다면 문제를 2-SAT문제로 모델링해 해결할 수 있을 것이다. 아래는 문제의 조건들을 2-CNF로 표현하는 과정이다.

 

우선 "각 간선의 적어도 한쪽 끝은 capital이어야 한다" 조건은 두 town의 번호가 xy일 때 (TxTy)로 표현할 수 있다.

 

"각 country에는 capital이 정확히 하나씩 있어야한다"는 조건은 "각 country에는 capital이 최대 하나 존재한다" 및 "각 country에는 capital이 최소 하나 존재한다"는 두 조건으로 나누어 생각할 수 있다. 한편, 후자 조건 없이 전자 제약만을 걸어 답을 얻은 다음 capital이 정해지지 않은 국가의 경우 아무 도시나 capital로 지정한다면 후자 조건이 같이 있는 경우의 답을 얻을 수 있고 이는 어렵지 않으므로 후자 조건 없이 전자 조건만을 이용한 해를 찾는 것으로 문제를 해결할 수 있게 된다.

 

이제 "각 country에는 capital이 최대 하나 존재한다" 조건을 CNF로 표현해보자. 어떤 country에 속한 town이 Tc1...Tcw라 하자. naive하게 2-CNF로 이 조건을 표현하는 한 가지 방법은 모든 1i,jw(ij)에 대하여 (TciTcj) 들의 논리곱으로 나타내는 것이다. 그러니 이러한 모든 식을 이용하기에는 그래프의 크기가 매우 커지므로 다른 방법을 생각해야한다.

 

새로운 bool 변수 Si를 정의해 그 값을 i 이하의 모든 Tci 들을 or한 값과 동치인 값으로 정의하자. 이 때 TciSi, SiSi+1Si Tci+1와 같은 논리관계를 부여하면 O(w)개의 노드 및 간선만을 추가해 위의 조건을 확인할 수 있게 된다. (자주 쓰이는 테크닉이므로 알아두자.)

 

위의 각 논리식들은 2-CNF로 나타낼 수 있으므로 2-SAT을 해결하는 선형 알고리즘을 이용해 이 문제를 해결할 수 있다. 시간복잡도는 O(N+M)이다.

 

그래프의 크기 및 입출력의 양이 많아 같은 알고리즘이더라도 속도가 느린 언어 또는 비효율적인 구현으로는 통과하기 어려운 듯하다. 이는 그래프를 모델링할 때 각 country에 capital이 최소 하나 존재 존재한다는 조건을 제외하고 별도의 처리로 조건을 만족시킨 이유이기도 하다. 혹시 그래도 시간 초과가 날 경우 불필요한 구현이 없는지 확인해 문제를 해결하자.

 

아래는 제출한 소스코드이다.

#define NODE 4000001
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int N, M, K; const int gap = 2000000; // 2-SAT 정점개수(TF분할 말고), T와F노드 사이 값의 차이
vector<int> G[NODE];
int dfi[NODE]; int dfidx = 1;
int sci[NODE]; int scidx = 1;
vector<int> stk;
int twosatdfs(int current) {
	stk.emplace_back(current);
	int ret = dfi[current] = dfidx++;
	for (auto node : G[current]) {
		if (sci[node]) continue;
		if (dfi[node]) ret = min(ret, dfi[node]);
		else ret = min(ret, twosatdfs(node));
	}

	if (ret == dfi[current]) {
		while (stk.back() != current) {
			sci[stk.back()] = scidx;
			stk.pop_back();
		}
		sci[stk.back()] = scidx++;
		stk.pop_back();
	}

	return ret;
}

bool ans[NODE];
bool TWOSAT() {
	for (int i = 1; i <= N; i++) {
		if (sci[i]) continue;
		twosatdfs(i);
	}
	for (int i = 1 + gap; i <= N + gap; i++) {
		if (sci[i]) continue;
		twosatdfs(i);
	}

	for (int i = 1; i <= N; i++) {
		if (sci[i] < sci[i + gap]) ans[i] = 1;
		else if (sci[i] > sci[i + gap]) ans[i] = 0;
		else return 0;
	}
	return 1;
}

int nxtid;
vector<int> towns[1000001];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N >> M >> K;
	while (M--) {
		int x, y; cin >> x >> y;
		G[x + gap].emplace_back(y);
		G[y + gap].emplace_back(x);
	}

	nxtid = N;

	for (int k = 1; k <= K; k++) {
		int T; cin >> T;

		nxtid++;
		int old; cin >> old;
		G[old].emplace_back(nxtid);
		G[nxtid + gap].emplace_back(old + gap);

		towns[k].emplace_back(old);

		for (int t = 1; t < T; t++) {
			nxtid++;
			int x; cin >> x;
			G[x].emplace_back(nxtid);
			G[nxtid + gap].emplace_back(x + gap);
			G[nxtid - 1].emplace_back(nxtid);
			G[nxtid + gap].emplace_back(nxtid - 1 + gap);
			G[nxtid - 1].emplace_back(x + gap);
			G[x].emplace_back(nxtid - 1 + gap);
			
			towns[k].emplace_back(x);

			old = x;
		}
	}

	N = nxtid;

	if (TWOSAT()) {
		cout << "TAK\n";
		for (int k = 1; k <= K; k++) {
			bool has_capital = 0;
			for (auto t : towns[k]) {
				if (ans[t]) has_capital = 1, cout << t << ' ';
			}
			if (!has_capital) cout << towns[k].back() << ' ';
		}
	}
	else cout << "NIE";
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 4138 // C++] Paper Route  (1) 2023.10.17
[BOJ 4139 // C++] Octagons  (0) 2023.10.16
[BOJ 18259 // C++] Greedy Pie Eaters  (0) 2023.10.14
[BOJ 18260 // C++] Bessie's Snow Cow  (0) 2023.10.13
[BOJ 11999 // C++] Milk Pails (Bronze)  (1) 2023.10.12

+ Recent posts