Suhwanc

 

acm-icpc 2020 서울 리저널에서 E번으로 출제되었던 문제이다. 

내 기억상으로 3번째로 많이 풀렸던 문제였는데.. 그때 우리는 풀지 못했었다 🥺

 

그것이 우리니까..

 

하지만 지금은 다르다!!

dp를 계속 풀다 생각나서 보니, 해답이 20분? 만에 떠올랐다. 디버깅 과정에서 많이 틀리긴 했는데,, 암튼 나 혼자 해결했다!

지금 생각해보니 그때 결과 보신 교수님은 우릴 어떻게 생각하셨을지... 😰 

 

문제 출처 : https://www.acmicpc.net/problem/20173

 

문제는 출처 참고하세요!

 

 

풀이

 

문제에서 컴퓨터가 잘못 판단할 경우는 수가 인접할 때 (1과 2, 2와 3 등) 밖에 없다.

따라서 모든 대결(?)을 볼 필요 없이 수가 다음과 같이 줄어들게 된다. 

(1, 2), (2, 3), (3, 4), ..., (n - 1, n)  

 

따라서 1과 n은 승수가 최대 1 차이나게 되고, 나머지 수는 2가 차이 날 수 있게 된다.

이렇게 보면 각 수당 경우의 수가 별로 없게 된다.

 

이제 각 수들의 입장에서 살펴보자.

 

어떤 수 i가 대결할 때는 총 2라운드에서 4가지 경우가 존재한다.

 

  • 1라운드 승, 2라운드 승
  • 1라운드 패, 2라운드 패
  • 1라운드 승, 2라운드 패
  • 1라운드 패, 2라운드 승

 

이때 어떤 수 i가 1과 n이 아니라면 1,2라운드 각각 2번씩 대결을 하게 되는데, 그 경우 4 x 4 = 16 가지 경우가 나온다.

하지만 이전 수 i-1에서 먼저 대결을 계산하고 결과를 넘겨준다면 i에서는 4가지 경우만 고려하면 된다.

(이 부분은 말이 좀 난해한데, 코드를 보면 이해가 쉬울 것이다.)

 

또한 어떤 수 i에 대한 대결을 확정 지었다면, 이 대결이 영향을 미치는 것은 오직 i+1의 대결이다.

반대로 i-1의 가능한 경우를 미리 조사했다면 i를 고려할 때 1 ~ i-2 에서의 대결 결과는 따로 볼 필요가 없다는 것이다.

 

따라서 dp[n][i][j] : 수 n이 (n, n+1)의 대결에서 1라운드에서 i번 승리했고, 2라운드에서 j번 승리할 수 있는가?로 생각해보자.

만약 (n-1, n) 의 대결에서 n이 1라운드 패, 2라운드 승이라 가정해보면

만약 v[n] = 2 라면 (n, n+1)의 대결에서 n이 1라운드 패, 2라운드 승을 가져와야 한다. (dp[n][0][1] = true)

반대로 n+1은 1라운드 승, 2라운드 패가 된다. (dp[n][1][0] = true)

 

이 결과를 다음 (n+1, n+2) 대결에서 넘겨주면 되는 것이다.

 

만약 이전 대결에서 1라운드 승, 2라운드 승인데, v[n] = 2 라면 이 때는 불가능하다. 한 번의 승부로 격차를 2승 이상 늘릴 수 없기 때문이다.

 

이 방법을 1 ~ n까지 하되, 1과 n은 따로 예외처리 과정이 필요하다.

배열의 크기가 최대 400만이며, dp 속성을 만족하기 때문에 메모이제이션 과정을 거치면 제한시간 내 해결 가능하다.

 

소스 코드(.cpp)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

bool dp[1000010][2][2];
int n;
vector<int>v;

// 현재 idx 수는 기계 1에서 l번, 기계 2에서 r번 이긴 상태.
void go(int idx, int l, int r){
	bool& ret = dp[idx][l][r];
	if(ret != false) return;
	ret = true;
	
	if(idx > n) return;
	
	//두 기계의 승수가 같다.
	if(v[idx] == 0){
		//한번의 승부로 맞출 수 없음
		if(l == r) { //승승 or 패패인 경우
			go(idx + 1, 1, 1);
			go(idx + 1, 0, 0);
		}
		else{
			if(l > r) go(idx + 1, 1, 0); //r번 승리 -> 다음에겐 l번 승리
			else go(idx + 1, 0, 1);
		}
	}
	else if(v[idx] == 1){ //왼쪽 오른쪽 다른 경우
		if(abs(l - r) == 1) {
			go(idx + 1, 1, 1);
			go(idx + 1, 0, 0);
		}
		else if(l == r){ // 승승, 패패인 경우
			go(idx + 1, 1, 0);
			go(idx + 1, 0, 1);
		}
	}
	else{ //v[i] == 2
		if(l == r) return;
		else if(l > r) go(idx + 1, 0, 1); //l번 승리
		else go(idx + 1, 1, 0);
	}
}
int main() {
	ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>n;
	v.resize(n + 1);
	for(int i=1; i<=n; i++) cin>>v[i];
	
	for(int i=1; i<=n; i++){
		if((i == 1 && v[1] > 1) || (i == n && v[n] > 1) || v[i] > 2){
			cout<<"NO\n";
			return 0;
		}
	}
	
	if(v[1] == 0){
		go(2, 0, 0);
		go(2, 1, 1);
	}
	else if(v[1] == 1){
		go(2, 0, 1);
		go(2, 1, 0);
	}
	
	bool flag = false;
	for(int i=0; i<2; i++){
		for(int j=0; j<2; j++){
			if(v[n] == 0 && (i == j) && dp[n][i][j]) flag = true;
			if(v[n] == 1 && (i != j) && dp[n][i][j]) flag = true;
		}
	}
	
	if(flag) cout<<"YES\n";
	else cout<<"NO\n";
	return 0;
}

'백준 문제풀이' 카테고리의 다른 글

백준 20176 Needle  (3) 2021.06.23
백준 13430번 합 구하기  (0) 2021.05.24
백준 1014번 컨닝  (0) 2021.05.17
백준 1086 박성원  (0) 2021.05.16
백준 1946번 신입사원  (0) 2020.08.05