Programming

[CF] Round #612 (Div. 2) _ 220211

minigb 2022. 3. 9. 03:13

https://codeforces.com/contest/1287

 

Dashboard - Codeforces Round #612 (Div. 2) - Codeforces

 

codeforces.com

아 ㅋㅋ

한 달 전의 내가 글 조금만 더 다듬고 올리려고 임시저장 해놨는데 그 후로 방치했다가 이제야 발견했다.

지금이라도 올려서 다행.

 

A.

A들 간의 간격 중 가장 큰 걸 구하면 된다.

근데 ('A'들 간의 인덱스 차 - 1)를 구해야 하는데 그냥 차이로 계산했다가 두 번 틀렸다. ㅠㅠ

int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int tc; cin >> tc;
	while (tc--) {
		int n; cin >> n;
		string s; cin >> s;
		int maxLen = 0;
		int before = -1;
		for (int i = 0; i < n; ++i) {
			if (s[i] == 'A') {
				if (before != -1 && i - before != 1) {
					maxLen = max(maxLen, i - before - 1);
				}
				before = i;
			}
		}
		
		if (before != -1 && before != n - 1) {
			maxLen = max(maxLen, n - before - 1);
		}

		cout << maxLen << kEndl;
	}
}

 

 

B.

n이 최대 1500이기 때문에 O(n^2) 풀이를 떠올리려고 했다.

카드 두 장을 고르고 나면 set을 만드는 데 필요한 카드의 종류는 유일하다는 걸 깨달았다.

그래서 카드 두 장을 골라서 모든 경우를 보는 데 n^2, 세 번째 카드를 만드는 데 k (k <= 30)의 시간이 소요되므로 O(n^2k)에 해결할 수 있다.

int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int n, k; cin >> n >> k;
	vector<string> arr(n);
	for (int i = 0; i < n; ++i) {
		cin >> arr[i];
	}
	sort(arr.begin(), arr.end());

	ll ans = 0;
	for (int i = 0; i < n; ++i) {
		const string& first = arr[i];
		for (int j = i + 1; j < n; ++j) {
			const string& second = arr[j];
			string finding = "";
			for (int idx = 0; idx < k; ++idx) {
				if (first[idx] == second[idx]) {
					finding += first[idx];
				}
				else {
					const char& a = first[idx], b = second[idx];
					if ((a == 'S' && b == 'E') || (a == 'E' && b == 'S')) {
						finding += 'T';
					}
					else if ((a == 'E' && b == 'T') || (a == 'T' && b == 'E')) {
						finding += 'S';
					}
					else {
						finding += 'E';
					}
				}
			}

			if (binary_search(arr.begin() + j + 1, arr.end(), finding)) {
				++ans;
			}
		}
	}

	cout << ans << kEndl;
}

재밌는 점은

set의 세 번째 카드를 구하면서 카드의 특성이 모두 다른 경우를 처리할 때

저렇게 3!의 경우를 모두 고려하는 하드코딩으로 짜기가 싫어서 처음에는

					map<char, bool> chk;
					chk['S'] = chk['E'] = chk['T'] = false;
					chk[first[idx]] = chk[second[idx]] = true;
					for (auto iter = chk.begin(); iter != chk.end(); ++iter) {
						if (iter->second == false) {
							finding += iter->first;
							break;
						}
					}

이렇게 했다.

근데 TLE 뜨길래 혹시나 해서 저렇게 if문을 이용하는 방법으로 바꿨더니 맞음...

 

 

C.

(이 부분이 한 달 전의 내가 글을 올리기 전에 다듬으려고 했던 부분이다.

당시에는 설명에서 너무 모호한 게 많아 보여서 그랬던 건데 지금 보니까 이것도 나쁘지 않아서 그냥 그대로 올린다.)

 

그리디로 접근했다.

연속하는 0들에 대한 정보를 저장하고, 우선순위에 따라 정렬한 뒤, 최선의 경우로 채워나간다.

 

다양한 상황이 존재할 수 있는데, 그중에서 각각의 최선과 최악의 경우를 생각해보면 0 or 1 or 2만큼 증가한다.

그러므로 일단은 0이 될 수 있는 상황부터 해결하고, 그다음에는 1, 그다음에는 2.

0이 될 수 있는 상황: 양옆이 같고, 그 안을 채울 수 있을 만큼 수들이 남아있는 경우

1이 될 수 있는 상황: 양옆이 다른 상황. 이때는 사실상 이게 최악이면서도 최선임.

2가 되는 상황: 양 옆이 같고, 그 안을 채울 수 있을만큼 수가 없음.

0 뭉치가 양 끝에 있다면? -> 최선이면 0, 최악이면 1. 그렇지만 그냥 이건 맨 마지막에 고려해야 함. 한쪽만 신경 쓰면 되기 때문에, 다른 경우들에 대한 최선의 상태를 우선 만들어놓고, 여기를 처리하는 게 이득이다.

 

그래서 순서는

1. 양 끝이 같은 경우는 0 or 2이므로, 0들로 처리할 수 있는 것들을 많이 만들어야 함. 그래서 counting이 작은 순으로 오름차순 정렬한 거고.

2. 최선을 다해서, 양 끝이 같은 경우를 처리한다.

3. 처리가 불가능한 경우가 생기면, 그때부터는 개수를 고려할 필요가 없다. 어차피 그 사이에서 parity가 바뀌는 지점이 무조건 존재한다는 게 중요한 거기 때문에. 그 사이에 구체적인 개수는 알 필요 없고.

 

연속하는 0들의 정보는

- counting: 연속하는 0들의 개수 (default = 0)

- same: 양 끝의 수들의 parity가 같은지의 여부 (default = true)

- remainder: 만약 parity가 같다면 그 값이 무엇인지 (default = false)

- end: 연속하는 0이 수열의 끝에 위치하는지의 여부 (default = false)

그리고 이걸 저장하기 위해 structure를 선언했다.

struct Diff {
	int counting;
	bool same;
	bool remainder;
	bool end;
};

 

예를 들어

10
1 0 0 5 0 0 0 6 3 0

라는 인풋이 있으면

첫 번째 연속하는 0들은 counting = 2; same = true; remainder = true; end = false;

두 번째는 counting = 3; same = false; remainder = false; end = false;

세 번째는 counting = 1; same = true; remainder = false; end = true;

이런 식으로 저장된다.

 

그리고 이 정보를 저장한 벡터를 정렬했는데, 우선순위는

1. end == false

2. same == true

3. counting이 작은 순서

이다.

 

그리고 이제 차례대로 최선을 다해서 그리디하게 처리하면 된다. 이때 0을 채우는 데 현재 사용할 수 있는 짝수와 홀수의 개수를 저장해둬야 한다.

same == true이면서 그 구간을 동일한 parity의 수로 채울 수 있다면, 즉 짝수/홀수의 개수가 충분하다면 이때 사용한 짝수/홀수 개수를 차감하기만 하면 된다.

만약 개수가 충분하지 않다면 중간에 parity가 바뀌는 경우가 생기는데, 이때 만약 이 구간의 end == true라면 한 번, false라면 두 번 생긴다.

이후 same == false인 구간에 대해서는 parity가 무조건 한 번 바뀌고, 이 외 고려사항은 없다.

처음에 데이터를 처리할 때 end = true면 same = true로 설정했기 때문에 same == false이면서 end == true인 경우는 고려하지 않아도 된다.

struct Diff {
	int counting;
	bool same;
	bool remainder;
	bool end;
	bool operator<(Diff comp) {
		if (end != comp.end) {
			return !end;
		}
		if (same == comp.same) {
			return counting < comp.counting;
		}
		return same;
	}
	Diff() {};
	Diff(int counting, bool same, bool remainder, bool end) {
		this->counting = counting;
		this->same = same;
		this->remainder = remainder;
		this->end = end;
	}
};

int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int n; cin >> n;
	vector<int> arr(n);
	vector<int> cnt(2);
	cnt[0] = n / 2; cnt[1] = n / 2 + n % 2;

	for (int i = 0; i < n; ++i) {
		cin >> arr[i];
		if (arr[i] != 0) {
			--cnt[arr[i] % 2];
		}
	}

	vector<Diff> data;
	int before = -1;
	int ans = 0;
	for (int i = 0; i < n; ++i) {
		if (arr[i] != 0) {
			if (before == -1 && i != 0) {
				data.push_back(Diff(i, true, arr[i] % 2, true));
			}
			else if (i != before + 1) {
				data.push_back(Diff(i - before - 1, arr[i] % 2 == arr[before] % 2, arr[i] % 2, false));
			}
			before = i;

			if (i - 1 >= 0 && arr[i - 1] != 0) {
				ans += (arr[i] % 2 != arr[i - 1] % 2);
			}
		}
	}

	if (n == 1) {
		cout << 0 << kEndl;
		return 0;
	}
	if (before == -1) {
		cout << 1 << kEndl;
		return 0;
	}
	if (before != n - 1) {
		data.push_back(Diff(n - before - 1, true, arr[before] % 2, true));
	}
	
	sort(data.begin(), data.end());

	for (const Diff& diff : data) {
		if (diff.same) {
			if (diff.counting <= cnt[diff.remainder]) {
				cnt[diff.remainder] -= diff.counting;
			}
			else {
				if (diff.end) {
					++ans;
				}
				else {
					ans += 2;
				}				
			}
		}
		else {
			++ans;
		}
	}

	cout << ans << kEndl;
}

 

그런데 알고보니 DP 문제였다.

ㅋㅋ!

문제 어렵게 풀기 장인.