본문 바로가기

code/2019 하계 알고리즘 캠프

1-B-D 백준 17406번 배열 돌리기4 (+ 배열 돌리기 1~3)

알고리즘 캠프에서 풀었던 다른 문제들:

 

알고리즘 캠프 인덱스

5일동안 풀었던 여러 유형의 백준 문제들 1일차 Brute Force 2일차 BFS 3일차 4일차 5일차 오전(A) A. 15650번 N과 M(2) B. 14501번 퇴사(+ 퇴사 2) 오후(B) A. 16922 로마숫자 만들기 B. 16917번 두 동전 C. 1693..

dongwook-chang.tistory.com

문제 링크:

배열 돌리기 1 16926

배열 돌리기 2 16927

배열 돌리기 3 16935

배열 돌리기 4 17406

 

배열 돌리기 1, 배열 돌리기 2

같은 문제지만 배열돌리기 2에서는 R의 상한이 높아지기 때문에 R을 회전 대상이 되는 layer의 둘레의 길이(bufi = 2 *(vert + hori))만큼 % 하여 연산 횟수를 최소화해야 제한 시간 내에 문제를 해결할 수 있다.

// trimming : R을 둘레의 길이만 큼 % 해주기
/* 1. 가로 반복 세로 반복 횟수는 l에 의존 */
/* 2. R을 l에 따른 둘레의 길이로 % 해서 회전 횟수 줄이기 */
/* 3. l의 상한은 가로 세로 중 하나에 의존하는 것이 아니라 둘의 min에 의존 */

#include <iostream>
#include <algorithm>

using namespace std;

int N, M, R;
int A[300][300], buf[1196], bufi;

int main() {
	ios_base::sync_with_stdio(0); cin.tie(0);
	cin >> N >> M >> R;
	for (int n = 0; n < N; ++n)
		for (int m = 0; m < M; ++m)
			cin >> A[n][m];
	
	for (int l = min(N, M) / 2 - 1; l >= 0 / 2; --l) {	/* 3 */
		int vert, hori;
		vert = M - 1 - 2 * l, hori = N - 1 - 2 * l;	/* 1 */
		int n, m;
		bufi = 0;  n = m = l;

		for (int i = 0; i < vert; ++i) {
			buf[bufi++] = A[n][m++];
		}
		for (int i = 0; i < hori; ++i) {
			buf[bufi++] = A[n++][m];
		}
		for (int i = 0; i < vert; ++i) {
			buf[bufi++] = A[n][m--];
		}
		for (int i = 0; i < hori; ++i) {
			buf[bufi++] = A[n--][m];
		}

		rotate(buf, buf + R % bufi, buf + bufi);	/* 2 */

		bufi = 0;  n = m = l;
		for (int i = 0; i < vert; ++i) {
			A[n][m++] = buf[bufi++];
		}
		for (int i = 0; i < hori; ++i) {
			A[n++][m] = buf[bufi++];
		}
		for (int i = 0; i < vert; ++i) {
			A[n][m--] = buf[bufi++];
		}
		for (int i = 0; i < hori; ++i) {
			A[n--][m] = buf[bufi++];
		}
	}

	for (int n = 0; n < N; ++n) {
		for (int m = 0; m < M; ++m)
			cout << A[n][m] << ' ';
		cout << '\n';
	}

	return 0;
}

(위에서부터 배열돌리기 1, 배열돌리기 2 채점 결과)

 

배열 돌리기 3

연산 간의 교환법칙이 성립할 것이라고 생각했으나 성립하지 않았다.(일부 연산들의 조합에 한해 성립하는 것 같다.) 연산 3과 4, 연산 5와 6은 동종의 연산이며 방향만 다르기 때문에 서로 상쇄 가능하다. 예를 들어 한 쌍의 각 연산들을 같은 횟수만큼 수행하면 그 결과는 수행하기 전과 동일하다. 이를 활용하여 연속한 1, 2, 3과 4, 5와 6의 개수를 세어 연산 횟수를 상쇄하려고 했으나 수행 속도는 빨라지지 않았다.

/* 1. 문제에서 연산자는 1부터 시작, 내 코드 0부터 시작 -> 입력 받을 때 수정해주기 */
/* 2. 연산 횟수(ops[op])에 상응하는 함수 호출 x -> 연산자에(op)에 상응하는 함수 호출 o */
/* 3. 출력 역시 원본 배열의 크기 N, M이 아니라 연산에 따라 변경된 새로운 배열의 크기 nN, nM 규격으로 */
/* 4. 서로 연관이 있는 연산 횟수를 가감하여 일원화하는 과정에서 연산횟수가 음수가 될 수 있기에 0 초과에서 0 아님으로 비교 연산자 변경 */
/* 5. 4와 마찬가지로 연산횟수가 음수일 수 있기 때문에 반복 횟수를 */

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int N, M, R, i[100][100], o[100][100];

#define loop(x,n) for(int x = 0; x < (n); ++x)

void one() {
	loop(n, N)
		loop(m, M)
		o[N - 1 - n][m] = i[n][m];
}

void two() {
	loop(n, N)
		loop(m, M)
		o[n][M - 1 - m] = i[n][m];
}

void three() {
	loop(n, N)
		loop(m, M)
		o[m][N - 1 - n] = i[n][m];
	swap(N, M);
}

void four() {
	loop(n, N)
		loop(m, M)
		o[M - 1 - m][n] = i[n][m];
	swap(N, M);
}

void five() {
	int qN = N / 2, qM = M / 2;
	vector<pair<int, int>> p = { { 0, 0 },{ 0, qM },{ qN, qM },{ qN, 0 } };

	loop(pi, 4)
		loop(n, qN)
			loop(m, qM)
				o[p[(pi + 1) % 4].first + n][p[(pi + 1) % 4].second + m] = i[p[pi].first + n][p[pi].second + m];
}

void six() {
	int qN = N / 2, qM = M / 2;
	vector<pair<int, int>> p = { { 0, 0 },{ 0, qM },{ qN, qM },{ qN, 0 } };

	loop(pi, 4)
		loop(n, qN)
			loop(m, qM)
				o[p[(pi + 3) % 4].first + n][p[(pi + 3) % 4].second + m] = i[p[pi].first + n][p[pi].second + m];

}

void(*func[6])() = { one, two, three, four, five, six };

int main() {
	ios_base::sync_with_stdio(0); cin.tie(0);
	cin >> N >> M >> R;
	loop(n, N)
		loop(m, M)
			cin >> i[n][m];

	loop(r, R - 1) {
		int op; cin >> op;
		(*func[op - 1])();

		loop(n, N) 	/* 3 */
			loop(m, M)		/* 3 */
				i[n][m] = o[n][m];
	}
	int op; cin >> op;
	(*func[op - 1])();

	loop(n, N) {	/* 3 */
		loop(m, M)		/* 3 */
			cout << o[n][m] << ' ';
		cout << '\n';
	}

	return 0;
}

아래 코드가 상쇄 버전

/* 1. 문제에서 연산자는 1부터 시작, 내 코드 0부터 시작 -> 입력 받을 때 수정해주기 */
/* 2. 연산 횟수(ops[op])에 상응하는 함수 호출 x -> 연산자에(op)에 상응하는 함수 호출 o */
/* 3. 출력 역시 원본 배열의 크기 N, M이 아니라 연산에 따라 변경된 새로운 배열의 크기 nN, nM 규격으로 */
/* 4. 서로 연관이 있는 연산 횟수를 가감하여 일원화하는 과정에서 연산횟수가 음수가 될 수 있기에 0 초과에서 0 아님으로 비교 연산자 변경 */
/* 5. 4와 마찬가지로 연산횟수가 음수일 수 있기 때문에 반복 횟수를 */
/* 6. 연산 1이 ops에 0으로 들어가면 빈칸 0과 구분하기 어려움 -> 연산 1을 1로 표현 */

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int N, M, R, ops[1000], i[100][100], o[100][100];
vector<pair<int, int>> reps;
int cyc[5] = { 2, 2, 4, 4 };	/* 6 */

#define loop(x,n) for(int x = 0; x < (n); ++x)

void one(int r) {
	loop(n, N)
		loop(m, M)
			o[N - 1 - n][m] = i[n][m];
}

void two(int r) {
	loop(n, N)
		loop(m, M)
			o[n][M - 1 - m] = i[n][m];
}

void three_four(int r) {
	loop(n, N)
		loop(m, M)
			o[m][N - 1 - n] = i[n][m];
	swap(N, M);
}

void five_six(int r) {
	int qN = N / 2, qM = M / 2;
	vector<pair<int, int>> p = { { 0, 0 },{ 0, qM },{ qN, qM },{ qN, 0 } };

	loop(pi, 4)
		loop(n, qN)
			loop(m, qM)
				o[p[(pi + r) % 4].first + n][p[(pi + r) % 4].second + m] = i[p[pi].first + n][p[pi].second + m];
}

void(*func[4])(int) = { one, two, three_four, five_six };

int main() {
	ios_base::sync_with_stdio(0); cin.tie(0);
	cin >> N >> M >> R;
	loop(n, N)
		loop(m, M)
			cin >> i[n][m];

	loop(r, R) 
		cin >> ops[r];

	loop(r, R) {
		int rep = 0, op = ops[r], opi;
		for (opi = 0; r + opi < R; ++opi)
			switch (op) {
			case 1:
				if (ops[r + opi] != 1) goto out;
				++rep;  break;
			case 2:
				if (ops[r + opi] != 2) goto out;
				++rep;  break;
			case 3: case 4:
				if (ops[r + opi] != 3 && ops[r + opi] != 4) goto out;
				if (ops[r + opi] == 3) ++rep;
				else --rep; break;
			case 5: case 6:
				if (ops[r + opi] != 5 && ops[r + opi] != 6) goto out;
				if (ops[r + opi] == 5) ++rep;
				else --rep; break;
			}
	out:
		r = r + opi - 1;
		switch (op) {
		case 1: op = 0; break;
		case 2: op = 1; break;
		case 3: case 4: op = 2; break;
		case 5: case 6: op = 3; break;
		}

		rep += 1000;
		rep %= cyc[op];

		reps.emplace_back(op, rep);
	}

	for(auto rep : reps){
		if (rep.first != 3) {
			loop(repi, rep.second) {
				(*func[rep.first])(rep.second);

				loop(n, N) 	/* 3 */
					loop(m, M)		/* 3 */
						i[n][m] = o[n][m];
			}
		}
		else {
			func[rep.first](rep.second);

			loop(n, N) 	/* 3 */
				loop(m, M)		/* 3 */
				i[n][m] = o[n][m];
		}
	}

	loop(n, N) {	/* 3 */
		loop(m, M)		/* 3 */
			cout << i[n][m] << ' ';
		cout << '\n';
	}

	return 0;
}

배열 돌리기 4

/* 1. 반대 방향으로 돌림 */
/* 2. A를 B에 복사하지 않고 A를 돌린 결과를 B에 할당하는 방식을 사용 -> 돌리지 않은 부분이 복사되지 않음 */

#include <iostream>
#define loop(x, n) for(int x = 0; x < (n); ++x)

using namespace std;

int N, M, K;
int A[50][50], B[50][50], op[6][3], s[6], buf[196];
bool v[6];
int mmrsum = 250000;

void OP(int k) {
	for (int l = 1; l <= op[k][2]; ++l) {
		int n = op[k][0] - 1 - l, m = op[k][1] - 1 - l, bufi = 0;
		loop(i, 2 * l) buf[bufi++] = B[n][m++];
		loop(i, 2 * l) buf[bufi++] = B[n++][m];
		loop(i, 2 * l) buf[bufi++] = B[n][m--];
		loop(i, 2 * l) buf[bufi++] = B[n--][m];
		n = op[k][0] - 1 - l, m = op[k][1] - 1 - l;	/* 1 */
		B[n][m++] = buf[bufi - 1];
		bufi = 0;
		loop(i, 2 * l - 1) B[n][m++] = buf[bufi++];
		loop(i, 2 * l) B[n++][m] = buf[bufi++];
		loop(i, 2 * l) B[n][m--] = buf[bufi++];
		loop(i, 2 * l) B[n--][m] = buf[bufi++];
	}
}

void BF(int k, int d) {
	if (d == K) {
		loop(n, N) loop(m, M) B[n][m] = A[n][m];	/* 2 */
		loop(k, K)
			OP(s[k]);
		int mrsum = 250000;
		loop(n, N) {
			int rsum = 0;
			loop(m, M) rsum += B[n][m];
			if (rsum < mrsum)
				mrsum = rsum;
		}
		if (mrsum < mmrsum)
			mmrsum = mrsum;
	}

	loop(k, K) {
		if (v[k]) continue;

		v[k] = 1; s[d] = k;
		BF(k + 1, d + 1);
		v[k] = 0;
	}
}

int main() {
	ios_base::sync_with_stdio(0); cin.tie(0);
	cin >> N >> M >> K;
	loop(n, N) loop(m, M) cin >> A[n][m];
	loop(k, K) {cin >> op[k][0] >> op[k][1] >> op[k][2];}
	BF(0, 0);
	cout << mmrsum;

	return 0;
}